Merge branch 1.7.3-fixes into master

This commit is contained in:
crschnick 2023-11-15 03:25:18 +00:00
parent 36a9a78896
commit b91eb0fda5
147 changed files with 1059 additions and 3717 deletions

View file

@ -1,21 +0,0 @@
**PRIVACY NOTICE**
**Last updated September 17, 2023**
This privacy notice for XPipe, in which ("**we**," "**us**," or "**our**") refers to XPipe UG (haftungsbeschränkt), describes how
and why we
might collect, store, use, and/or share ("**process**") your information when you use our services ("**Services**"),
such as when you:
* Download and use our application (XPipe)
**1\. WHAT INFORMATION DO WE COLLECT?**
We do not process personal or sensitive information.
Note that there exist a separate privacy policy in case you choose to submit an issue report through the included issue reporter.
You can find the issue reporter privacy policy [here](/app/src/main/resources/io/xpipe/app/resources/misc/report_privacy_policy.md) in this repository, but it will also be shown within the issue reporter.
**2\. HOW CAN YOU CONTACT US ABOUT THIS NOTICE?**
If you have questions or comments, you may contact us by email at hello@xpipe.io.

View file

@ -120,9 +120,9 @@ This mainly concerns the features only available in the professional tier and th
You have more questions? Then check out the [FAQ](https://xpipe.io/faq).
For information about the security model of XPipe, see the [security page](/SECURITY.md).
For information about the security model of XPipe, see the [security page](https://docs.xpipe.io/security).
For information about the privacy policy of XPipe, see the [privacy page](/PRIVACY.md).
For information about the privacy policy of XPipe, see the [privacy page](https://docs.xpipe.io/privacy-policy).
In case you're interested in development, check out the [contributing page](/CONTRIBUTING.md).

View file

@ -1,163 +0,0 @@
# Security
Due to its nature, XPipe has to handle a lot of sensitive information.
This can range from passwords for all kinds of servers, to SSH keys, and more.
Therefore, the security model of XPipe plays a very important role.
This document summarizes the approach of XPipe when it comes to the security of your sensitive information.
If any of your questions are left unanswered by this document, feel free to file an
issue report so your question can be answered individually and can also potentially be included in this document.
## Reporting a security vulnerability
If you believe that you found a security vulnerability in XPipe,
you can make use of
the [private security report feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)
of GitHub.
## Security assumptions
The general assumption is that the system on which XPipe runs on is not badly infected.
This refers to your local system on which you installed XPipe, not any remote systems that you then connect to.
If your local system is infected to an extent where malicious programs can modify the
file system and other installed programs like XPipe,
then there is no technical way of preventing malicious programs to also infect XPipe and the connected systems as well.
## Reliance on other programs
XPipe essentially delegates any form of connection and shell handling to your existing command-line tools.
It does not come with any remote handling capabilities of its own.
Therefore, any used command-line program should be secure.
If for example your `ssh` command-line program or its connections are susceptible to MITM attacks or
vulnerable in any other way, there is no way for XPipe to guarantee that the sensitive information can be kept secure.
It is your responsibility to use the programs in a secure environment and keep them up to date with security patches and
more.
XPipe can only be as secure as your underlying command-line tools itself.
XPipe calls these programs almost exactly as you would do manually in your terminal
with some a few additional parameters to automatically pass login information
and adapt the environment to make it work properly.
The called program therefore automatically uses your
system configuration for it, e.g. your system SSH configs.
XPipe does not perform any validation or version checking for the programs it calls.
For example, when establishing an ssh connection through XPipe, it will straight up call `ssh user@host <options>`.
It is assumed that this `ssh` executable is secure and the one that you actually want to use.
## Data security and privacy
The general approach of XPipe can be summarized as follows:
- Any sensitive information should be kept as secure as possible exclusively on your local machine,
both while XPipe is running and also not running
- When sensitive information is required on another remote system that is connected through XPipe, that information
should be transferred and
remain there as briefly and securely as possible
- No sensitive information should be sent to any other server outside your network of trusted connections
### Storage of sensitive information
All XPipe data is exclusively stored on your local machine at `~/.xpipe/storage`. You can choose to change this storage location in the settings menu.
You have the option to either fetch any sensitive information like passwords from outside sources like prompts or password managers. In that case, XPipe doesn't have to store any of that information itself.
In case you choose to store passwords within XPipe, all sensitive information is encrypted when it is saved to disk on your local machine using AES with either:
- A custom master key that can be set by you in the settings menu
(This option is only as secure as the password you choose)
- A somewhat dynamically generated key (This option can be reverse
engineered though, there is no way of perfectly securing your data without any custom key)
### Passing of sensitive information
When any kind of login information is required by a command-line program, it has to be passed to it somehow. If the program runs on your local system, the data does not leave your local system. If login information is required on a remote system, then that data must be transferred to that remote system.
In case a program accepts password input via stdin, this process is relatively straightforward. Then the passed sensitive information is just written into the stdin of the program and does not show up in any history or file system.
When a program only accepts password input via an environment variable or an askpass program, a self deleting password supplier script file is generated by XPipe.
This script contains the encrypted password and will supply the password to the target program exactly once when invoked and immediately deletes itself afterward.
This behavior ensures that there is no leftover password script after an operation is performed.
As a secondary measure, for cases in which the calling program crashes and is not able to execute the script and therefore doesn't delete the password script, the generated script directory is also frequently cleaned.
As a result, no sensitive information of yours should show up in any kind of shell history or on any file system.
### The purpose of shell scripts
Whenever you open a remote connection in a terminal from XPipe, your terminal sometimes shows the name of a script located in your temp directory in the title bar to indicate that you're currently executing it.
The naming scheme of these scripts is usually something like `exec-<id>.(bat|sh|ps1)`.
This is intended as these scripts contain all commands that are required to realize the functionality of connecting and initializing the shell environment.
These scripts do not contain any sensitive information, you are free to inspect them yourselves in the temp directory.
In case a script connects to a remote system and passes login information to a program via variables or askpass
programs, it automatically becomes useless after being invoked once (See [above](#passing-of-sensitive-information)).
As the script is run immediately after it is created initially, e.g. when using the `Open in terminal` functionality, it becomes useless pretty much instantly so any attacker doesn't obtain any sensitive information from it.
### Logging
By default, XPipe creates log files located in `~/.xpipe/logs`. These log files do not contain any sensitive information.
If you choose to launch XPipe in debug mode, these logs are printed to the console instead and will contain a lot more and finer grained information, some of which might be sensitive.
### Issue reports
Whenever an error occurs within XPipe or you choose to open the error reporter dialog, you have the option to automatically send an error report with optional feedback and attachments.
This error report does not contain any sensitive information, unless you explicitly choose to attach log files.
## Isolation of systems
Any infected remote system should be isolated enough such that any infection can't spread through XPipe.
### User isolation
All relevant files like configuration files and other required temporary files are only accessible by the current user.
Any other user on a system can't read or write them unless they have root/Administrator privileges.
### Isolation of remote systems
When you add a remote system as a host within XPipe, it is implicitly assumed that you trust this system.
Any required login information is sent to and handled on that remote host when required,
so it would be possible for malicious program with sufficient privileges to obtain any information sent to that host.
This would require an attacker to be able to access files of the user that is used to log into the remote system.
It should however not be possible for any malicious program on the remote host to obtain
other information stored by XPipe that is not explicitly sent to that host.
## Antivirus programs
### Windows Defender
It may occasionally happen that Windows Defender warns and
even sometimes deletes XPipe due to it identifying the application as malware.
The reason for this is simple: The application is not signed with an EV code signing
certificate as this would require a company for XPipe to be set up and would also cost around 600$+ per year.
If XPipe was signed with such a certificate, as are most Windows applications distributed by companies, all warnings
would go away automatically.
The Windows Defender / Windows SmartScreen system is essentially pay-to-win here.
Just paying the appropriate amount will automatically whitelist your application (even it is unsafe / essentially
malware)
while not paying will often blacklist it, bullying you into buying it.
You can read more about this system in [this StackExchange post](https://security.stackexchange.com/a/139520).
The manual whitelisting process without an EV certificate is purposely made difficult and essentially useless.
The Windows Defender detection rules are garbage and not deterministic, i.e.
an identical application can be flagged on one system but not the other, even though both are connected to the internet
and the Microsoft services.
In summary, don't rely on Windows Defender to be accurate when it comes to false-positives.
### macOS
On macOS the application bundle is signed and notarized and will therefore not emit any warnings.
For macOS this process does not require a company to be
set up and also only costs 125$ per year and is therefore much easier to accomplish.
### Windows antivirus programs
In some cases, it might occur that your antivirus program flags XPipe as malware.
This is due to the fact that XPipe launches shells and executes various commands in them,
which can be interpreted as malicious activity as some viruses use
the same approach and does lead to some false-positives.
For this reason, all artifacts of every release are automatically uploaded and analyzed on VirusTotal,
so uploading the release you downloaded to VirusTotal should instantly show analysis results.
From there you should be able to get a more accurate overview over the actual threat level of XPipe.
If such a detection also happens on your end, you might have to
explicitly whitelist XPipe in order for it to work correctly.
Having access to shells is necessary for XPipe, there is no fallback alternative built in that does not launch shells.

View file

@ -110,7 +110,8 @@ project.ext {
"-Xmx8g",
"-Dio.xpipe.app.arch=$rootProject.arch",
"-Dfile.encoding=UTF-8",
'-XX:+UseZGC',
// Disable this for now as it requires Windows 10+
// '-XX:+UseZGC',
"-Dvisualvm.display.name=XPipe"
]
}
@ -137,7 +138,7 @@ application {
run {
systemProperty 'io.xpipe.app.useVirtualThreads', 'false'
systemProperty 'io.xpipe.app.mode', 'gui'
systemProperty 'io.xpipe.app.dataDir', "$projectDir/local_git3/"
systemProperty 'io.xpipe.app.dataDir', "$projectDir/local_git8/"
systemProperty 'io.xpipe.app.writeLogs', "true"
systemProperty 'io.xpipe.app.writeSysOut', "true"
systemProperty 'io.xpipe.app.developerMode', "true"

View file

@ -2,6 +2,7 @@ package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.util.FailableRunnable;
import javafx.beans.property.Property;
@ -19,6 +20,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class BrowserClipboard {
@ -27,6 +29,11 @@ public class BrowserClipboard {
UUID uuid;
FileSystem.FileEntry baseDirectory;
List<FileSystem.FileEntry> entries;
public String toClipboardString() {
return entries.stream().map(fileEntry -> "\"" + fileEntry.getPath() + "\"").collect(
Collectors.joining(ShellDialects.getPlatformDefault().getNewLine().getNewLineString()));
}
}
public static final Property<Instance> currentCopyClipboard = new SimpleObjectProperty<>();
@ -67,9 +74,9 @@ public class BrowserClipboard {
@SneakyThrows
public static ClipboardContent startDrag(FileSystem.FileEntry base, List<FileSystem.FileEntry> selected) {
var content = new ClipboardContent();
var idea = UUID.randomUUID();
currentDragClipboard = new Instance(idea, base, new ArrayList<>(selected));
content.putString(idea.toString());
var id = UUID.randomUUID();
currentDragClipboard = new Instance(id, base, new ArrayList<>(selected));
content.putString(currentDragClipboard.toClipboardString());
return content;
}
@ -88,9 +95,13 @@ public class BrowserClipboard {
return null;
}
if (currentDragClipboard == null) {
return null;
}
try {
var idea = UUID.fromString(dragboard.getString());
if (idea.equals(currentDragClipboard.uuid)) {
var s = dragboard.getString();
if (s != null && s.equals(currentDragClipboard.toClipboardString())) {
var current = currentDragClipboard;
currentDragClipboard = null;
return current;

View file

@ -7,10 +7,13 @@ import io.xpipe.app.browser.icon.DirectoryType;
import io.xpipe.app.browser.icon.FileIconManager;
import io.xpipe.app.browser.icon.FileType;
import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
@ -26,12 +29,12 @@ import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.DragEvent;
import javafx.scene.layout.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -55,42 +58,35 @@ public class BrowserComp extends SimpleComp {
FileIconManager.loadIfNecessary();
});
var bookmarksList = new BrowserBookmarkList(model).createRegion();
VBox.setVgrow(bookmarksList, Priority.ALWAYS);
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage())
.hide(PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
var bookmarksList = new BrowserBookmarkList(model).vgrow();
var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide(
PlatformThread.sync(Bindings.createBooleanBinding(() -> {
if (model.getOpenFileSystems().size() == 0) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
if (model.getMode().isChooser()) {
return true;
}
// Also show on local
if (model.getSelected().getValue() != null) {
// return model.getSelected().getValue().isLocal();
}
// Also show on local
if (model.getSelected().getValue() != null) {
// return model.getSelected().getValue().isLocal();
}
return false;
},
model.getOpenFileSystems(),
model.getSelected())))
.createRegion();
localDownloadStage.setPrefHeight(200);
localDownloadStage.setMaxHeight(200);
var vertical = new VBox(bookmarksList, localDownloadStage);
vertical.setFillWidth(true);
return false;
}, model.getOpenFileSystems(), model.getSelected())));
localDownloadStage.prefHeight(200);
localDownloadStage.maxHeight(200);
var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage));
var splitPane = new SplitPane(vertical, createTabs());
splitPane
.widthProperty()
.addListener(
// set sidebar width in pixels depending on split pane width
(obs, old, val) -> splitPane.setDividerPosition(0, 360 / splitPane.getWidth()));
var r = addBottomBar(splitPane);
var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth(
AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange(
AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> {
struc.getLeft().setMinWidth(200);
struc.getLeft().setMaxWidth(500);
});
var r = addBottomBar(splitPane.createRegion());
r.getStyleClass().add("browser");
// AppFont.small(r);
return r;
@ -108,16 +104,12 @@ public class BrowserComp extends SimpleComp {
selected.setSpacing(10);
model.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
PlatformThread.runLaterIfNeeded(() -> {
selected.getChildren()
.setAll(c.getList().stream()
.map(s -> {
var field =
new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(400);
return field;
})
.toList());
selected.getChildren().setAll(c.getList().stream().map(s -> {
var field = new TextField(s.getRawFileEntry().getPath());
field.setEditable(false);
field.setPrefWidth(500);
return field;
}).toList());
});
});
var spacer = new Spacer(Orientation.HORIZONTAL);
@ -135,15 +127,14 @@ public class BrowserComp extends SimpleComp {
return layout;
}
private Node createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(
Comp.of(() -> createTabPane()),
private Comp<?> createTabs() {
var multi = new MultiContentComp(Map.<Comp<?>, ObservableValue<Boolean>>of(Comp.of(() -> createTabPane()),
BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())),
new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)),
Bindings.createBooleanBinding(() -> {
return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser();
}, model.getOpenFileSystems())));
return multi.createRegion();
return multi;
}
private TabPane createTabPane() {
@ -163,8 +154,7 @@ public class BrowserComp extends SimpleComp {
map.put(v, t);
tabs.getTabs().add(t);
});
tabs.getSelectionModel()
.select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue()));
// Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually!
var modifying = new SimpleBooleanProperty();
@ -180,9 +170,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(newValue))
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue))
.findAny()
.map(Map.Entry::getKey)
.orElse(null);
@ -197,9 +187,9 @@ public class BrowserComp extends SimpleComp {
return;
}
var toSelect = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getKey().equals(newValue))
var toSelect = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue))
.findAny()
.map(Map.Entry::getValue)
.orElse(null);
@ -238,9 +228,9 @@ public class BrowserComp extends SimpleComp {
tabs.getTabs().addListener((ListChangeListener<? super Tab>) c -> {
while (c.next()) {
for (var r : c.getRemoved()) {
var source = map.entrySet().stream()
.filter(openFileSystemModelTabEntry ->
openFileSystemModelTabEntry.getValue().equals(r))
var source = map.entrySet()
.stream()
.filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r))
.findAny()
.orElse(null);
@ -263,19 +253,14 @@ public class BrowserComp extends SimpleComp {
ring.setMinSize(16, 16);
ring.setPrefSize(16, 16);
ring.setMaxSize(16, 16);
ring.progressProperty()
.bind(Bindings.createDoubleBinding(
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
ring.progressProperty().bind(Bindings.createDoubleBinding(() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore());
var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion();
tab.graphicProperty()
.bind(Bindings.createObjectBinding(
() -> {
return model.getBusy().get() ? ring : logo;
},
PlatformThread.sync(model.getBusy())));
tab.graphicProperty().bind(Bindings.createObjectBinding(() -> {
return model.getBusy().get() ? ring : logo;
}, PlatformThread.sync(model.getBusy())));
tab.setText(model.getName());
tab.setContent(new OpenFileSystemComp(model).createSimple());
@ -301,9 +286,7 @@ public class BrowserComp extends SimpleComp {
c.getStyleClass().add(color.getId());
}
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
c.addEventHandler(
DragEvent.DRAG_ENTERED,
mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
c.addEventHandler(DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab)));
});
}
});

View file

@ -88,9 +88,6 @@ public final class BrowserFileListModel {
.toList()
: all.getValue();
Comparator<BrowserEntry> tableComparator = comparatorProperty.getValue();
var comparator =
tableComparator != null ? FILE_TYPE_COMPARATOR.thenComparing(tableComparator) : FILE_TYPE_COMPARATOR;
var listCopy = new ArrayList<>(filtered);
sort(listCopy);
shown.setValue(listCopy);

View file

@ -18,7 +18,6 @@ import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@Getter
@ -38,6 +37,7 @@ public class BrowserModel {
selected.addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
selection.clear();
return;
}
@ -60,11 +60,13 @@ public class BrowserModel {
public void reset() {
var list = new ArrayList<BrowserSavedState.Entry>();
openFileSystems.forEach(model -> {
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) {
list.add(new BrowserSavedState.Entry(model.getEntry().get().getUuid(), model.getCurrentPath().get()));
}
});
synchronized (BrowserModel.this) {
openFileSystems.forEach(model -> {
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) {
list.add(new BrowserSavedState.Entry(model.getEntry().get().getUuid(), model.getCurrentPath().get()));
}
});
}
// Don't override state if it is empty
if (list.size() == 0) {
@ -86,8 +88,11 @@ public class BrowserModel {
}
var chosen = new ArrayList<>(selection);
for (OpenFileSystemModel openFileSystem : openFileSystems) {
closeFileSystemAsync(openFileSystem);
synchronized (BrowserModel.this) {
for (OpenFileSystemModel openFileSystem : openFileSystems) {
closeFileSystemAsync(openFileSystem);
}
}
if (chosen.size() == 0) {
@ -101,9 +106,6 @@ public class BrowserModel {
public void closeFileSystemAsync(OpenFileSystemModel open) {
ThreadHelper.runAsync(() -> {
if (Objects.equals(selected.getValue(), open)) {
selected.setValue(null);
}
open.closeSync();
synchronized (BrowserModel.this) {
openFileSystems.remove(open);
@ -111,33 +113,7 @@ public class BrowserModel {
});
}
public void openExistingFileSystemIfPresent(DataStoreEntryRef<? extends FileSystemStore> store) {
var found = openFileSystems.stream().filter(model -> Objects.equals(model.getEntry(), store)).findFirst();
if (found.isPresent()) {
selected.setValue(found.get());
} else {
openFileSystemAsync(store, null, null);
}
}
public void openFileSystemAsync(DataStoreEntryRef<? extends FileSystemStore> store, String path, BooleanProperty externalBusy) {
// // Prevent multiple tabs in non browser modes
// if (!mode.equals(Mode.BROWSER)) {
// ThreadHelper.runFailableAsync(() -> {
// var open = openFileSystems.size() > 0 ? openFileSystems.get(0) : null;
// if (open != null) {
// open.closeSync();
// openFileSystems.remove(open);
// }
//
// var model = new OpenFileSystemModel(this, store);
// openFileSystems.add(model);
// selected.setValue(model);
// model.switchSync(store);
// });
// return;
// }
if (store == null) {
return;
}
@ -146,15 +122,15 @@ public class BrowserModel {
OpenFileSystemModel model;
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
model = new OpenFileSystemModel(this, store);
model.initFileSystem();
model.initSavedState();
// Prevent multiple calls from interfering with each other
synchronized (BrowserModel.this) {
model = new OpenFileSystemModel(this, store);
model.initFileSystem();
model.initSavedState();
openFileSystems.add(model);
// The tab pane doesn't automatically select new tabs
selected.setValue(model);
}
openFileSystems.add(model);
selected.setValue(model);
}
if (path != null) {
model.initWithGivenDirectory(path);

View file

@ -79,7 +79,7 @@ public class BrowserNavBar extends SimpleComp {
struc.get().setPromptText("Overview of " + model.getName());
})
.shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> {
.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN), s -> {
s.get().requestFocus();
})
.accessibleText("Current path");

View file

@ -6,8 +6,10 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileSystem;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
@ -27,22 +29,33 @@ public class BrowserOverviewComp extends SimpleComp {
@Override
@SneakyThrows
protected Region createSimple() {
// The open file system might have already been closed
if (model.getFileSystem() == null) {
return new Region();
}
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
// TODO: May be move this into another thread
var common = sc.getOsType().determineInterestingPaths(sc).stream()
.map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
.filter(entry -> {
try {
return sc.getShellDialect()
.directoryExists(sc, entry.getPath())
.executeAndCheck();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList();
var commonOverview = new BrowserFileOverviewComp(model, FXCollections.observableArrayList(common), false);
var commonPlatform = FXCollections.<FileSystem.FileEntry>observableArrayList();
ThreadHelper.runFailableAsync(() -> {
var common = sc.getOsType().determineInterestingPaths(sc).stream()
.map(s -> FileSystem.FileEntry.ofDirectory(model.getFileSystem(), s))
.filter(entry -> {
try {
return sc.getShellDialect()
.directoryExists(sc, entry.getPath())
.executeAndCheck();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList();
Platform.runLater(() -> {
commonPlatform.setAll(common);
});
});
var commonOverview = new BrowserFileOverviewComp(model, commonPlatform, false);
var commonPane = new SimpleTitledPaneComp(AppI18n.observable("common"), commonOverview)
.apply(struc -> VBox.setVgrow(struc.get(), Priority.NEVER));

View file

@ -73,8 +73,8 @@ public class BrowserTransferComp extends SimpleComp {
.apply(struc -> struc.get().setSpacing(10)),
button -> {
var p = new AnchorPane(button);
AnchorPane.setRightAnchor(button, 20.0);
AnchorPane.setTopAnchor(button, 20.0);
AnchorPane.setRightAnchor(button, 10.0);
AnchorPane.setTopAnchor(button, 10.0);
p.setPickOnBounds(false);
return p;
});
@ -155,6 +155,6 @@ public class BrowserTransferComp extends SimpleComp {
});
}),
PlatformThread.sync(stage.getDownloading()));
return stack.createRegion();
return stack.styleClass("transfer").createRegion();
}
}

View file

@ -83,7 +83,7 @@ public class BrowserWelcomeComp extends SimpleComp {
ThreadHelper.runAsync(() -> {
model.restoreState(e, disable);
});
}).disable(disable).styleClass("color-box").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false);
}).accessibleText(DataStorage.get().getStoreDisplayName(entry.get())).disable(disable).styleClass("color-box").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false);
}).apply(struc -> {
VBox vBox = (VBox) struc.get().getContent();
vBox.setSpacing(10);
@ -102,7 +102,7 @@ public class BrowserWelcomeComp extends SimpleComp {
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
model.restoreState(state);
actionEvent.consume();
}).grow(true, false);
}).grow(true, false).accessibleTextKey("restoreAllSessions");
layout.getChildren().add(tile.createRegion());
return layout;

View file

@ -1,12 +1,11 @@
package io.xpipe.app.browser;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.ConnectionFileSystem;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.LocalStore;
import java.nio.file.Files;
import java.nio.file.Path;
@ -15,28 +14,6 @@ import java.util.List;
public class FileSystemHelper {
public static String getStartDirectory(OpenFileSystemModel model) throws Exception {
// Handle special case when file system creation has failed
if (model.getFileSystem() == null) {
return null;
}
ConnectionFileSystem fileSystem = (ConnectionFileSystem) model.getFileSystem();
var current = !model.isLocal()
? fileSystem
.getShellControl()
.executeSimpleStringCommand(
fileSystem.getShellControl().getShellDialect().getPrintWorkingDirectoryCommand())
: fileSystem
.getShell()
.get()
.getOsType()
.getHomeDirectory(fileSystem.getShell().get());
var r = resolveDirectoryPath(model, evaluatePath(model, adjustPath(model, current)));
validateDirectoryPath(model, r);
return r;
}
public static String adjustPath(OpenFileSystemModel model, String path) {
if (path == null) {
return null;

View file

@ -9,9 +9,8 @@ import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
public interface FileType {
@ -45,10 +44,10 @@ public interface FileType {
return "." + r;
})
.toList();
.collect(Collectors.toSet());
var darkIcon = split[2].trim();
var lightIcon = split.length > 3 ? split[3].trim() : darkIcon;
ALL.add(new FileType.Simple(id, lightIcon, darkIcon, filter.toArray(String[]::new)));
ALL.add(new FileType.Simple(id, lightIcon, darkIcon, filter));
}
}
});
@ -59,9 +58,9 @@ public interface FileType {
private final String id;
private final IconVariant icon;
private final String[] endings;
private final Set<String> endings;
public Simple(String id, String lightIcon, String darkIcon, String... endings) {
public Simple(String id, String lightIcon, String darkIcon, Set<String> endings) {
this.icon = new IconVariant(lightIcon, darkIcon);
this.id = id;
this.endings = endings;
@ -73,8 +72,7 @@ public interface FileType {
return false;
}
return Arrays.stream(endings)
.anyMatch(ending -> entry.getPath().toLowerCase().endsWith(ending.toLowerCase()));
return endings.contains(entry.getPath().toLowerCase(Locale.ROOT));
}
@Override

View file

@ -49,6 +49,7 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
button.setGraphic(graphic);
button.getStyleClass().add("dropdown-comp");
button.setAccessibleText("Dropdown actions");
return new SimpleCompStructure<>(button);
}

View file

@ -35,6 +35,7 @@ public class ListSelectorComp<T> extends SimpleComp {
for (var v : values) {
var cb = new CheckBox(null);
cbs.add(cb);
cb.setAccessibleText(toString.apply(v));
cb.setSelected(selected.contains(v));
cb.selectedProperty().addListener((c, o, n) -> {
if (n) {

View file

@ -34,11 +34,6 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
var loadingOverlay = new StackPane(loading);
loadingOverlay.getStyleClass().add("loading-comp");
loadingOverlay.getStyleClass().add("modal-pane");
loadingOverlay.visibleProperty().addListener((observable, oldValue, newValue) -> {
r.setOpacity(newValue ? r.getOpacity() * 0.25 : Math.min(1.0, r.getOpacity() * 4));
});
loadingOverlay.setVisible(showLoading.getValue());
var listener = new ChangeListener<Boolean>() {

View file

@ -6,8 +6,8 @@ import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.core.process.OsNameState;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.process.ShellStoreState;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@ -41,11 +41,11 @@ public class OsLogoComp extends SimpleComp {
}
var ps = wrapper.getPersistentState().getValue();
if (!(ps instanceof ShellStoreState sss)) {
if (!(ps instanceof OsNameState ons)) {
return null;
}
return getImage(sss.getOsName());
return getImage(ons.getOsName());
},
wrapper.getPersistentState(), state));
var hide = BindingsHelper.map(img, s -> s != null);

View file

@ -51,6 +51,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
});
});
});
b.accessibleText(e.name());
vbox.getChildren().add(b.createRegion());
});
@ -64,7 +65,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
}
UserReportComp.show(event.build());
})
.apply(new FancyTooltipAugment<>("reportIssue"));
.apply(new FancyTooltipAugment<>("reportIssue")).accessibleTextKey("reportIssue");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@ -73,7 +74,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB))
.apply(new FancyTooltipAugment<>("visitGithubRepository"));
.apply(new FancyTooltipAugment<>("visitGithubRepository")).accessibleTextKey("visitGithubRepository");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@ -92,7 +93,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD))
.apply(new FancyTooltipAugment<>("discord"));
.apply(new FancyTooltipAugment<>("discord")).accessibleTextKey("discord");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});
@ -101,7 +102,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
{
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded())
.apply(new FancyTooltipAugment<>("updateAvailableTooltip"));
.apply(new FancyTooltipAugment<>("updateAvailableTooltip")).accessibleTextKey("updateAvailableTooltip");
b.apply(struc -> {
AppFont.setSize(struc.get(), 2);
});

View file

@ -0,0 +1,81 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Region;
import lombok.Value;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public class SideSplitPaneComp extends Comp<SideSplitPaneComp.Structure> {
private final Comp<?> left;
private final Comp<?> center;
private Double initialWidth;
private Consumer<Double> onDividerChange;
public SideSplitPaneComp(Comp<?> left, Comp<?> center) {
this.left = left;
this.center = center;
}
@Override
public Structure createBase() {
var c = center.createRegion();
var sidebar = left.createRegion();
if (initialWidth != null) {
sidebar.setPrefWidth(initialWidth);
}
var r = new SplitPane(sidebar, c);
AtomicBoolean setInitial = new AtomicBoolean(false);
r.widthProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.doubleValue() <= 0) {
return;
}
if (!setInitial.get() && initialWidth != null) {
r.getDividers().get(0).setPosition(initialWidth / newValue.doubleValue());
setInitial.set(true);
}
});
SplitPane.setResizableWithParent(sidebar, false);
r.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> {
if (r.getWidth() <= 0) {
return;
}
if (onDividerChange != null) {
onDividerChange.accept(newValue.doubleValue() * r.getWidth());
}
});
r.getStyleClass().add("side-split-pane-comp");
return new Structure(sidebar, c, r, r.getDividers().get(0));
}
public SideSplitPaneComp withInitialWidth(double val) {
this.initialWidth = val;
return this;
}
public SideSplitPaneComp withOnDividerChange(Consumer<Double> onDividerChange) {
this.onDividerChange = onDividerChange;
return this;
}
@Value
public static class Structure implements CompStructure<SplitPane> {
Region left;
Region center;
SplitPane pane;
SplitPane.Divider divider;
@Override
public SplitPane get() {
return pane;
}
}
}

View file

@ -60,6 +60,7 @@ public class DsStoreProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>
ComboBox<Node> cb = comboBox.build();
cb.getStyleClass().add("data-source-type");
cb.getStyleClass().add("choice-comp");
cb.setAccessibleText("Choose connection type");
return new SimpleCompStructure<>(cb);
}
}

View file

@ -1,6 +1,5 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.store.GuiDsStoreCreator;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.DataStoreProviders;
@ -52,7 +51,7 @@ public class StoreCreationMenu {
cmd.setGraphic(new FontIcon("mdi2c-code-greater-than"));
cmd.textProperty().bind(AppI18n.observable("addCommand"));
cmd.setOnAction(event -> {
GuiDsStoreCreator.showCreation(null,
GuiDsStoreCreator.showCreation(DataStoreProviders.byName("cmd").orElseThrow(),
v -> DataStoreProvider.CreationCategory.COMMAND.equals(v.getCreationCategory()));
event.consume();
});
@ -85,7 +84,7 @@ public class StoreCreationMenu {
script.setGraphic(new FontIcon("mdi2s-script-text-outline"));
script.textProperty().bind(AppI18n.observable("addScript"));
script.setOnAction(event -> {
GuiDsStoreCreator.showCreation(null,
GuiDsStoreCreator.showCreation(DataStoreProviders.byName("script").orElseThrow(),
v -> DataStoreProvider.CreationCategory.SCRIPT.equals(v.getCreationCategory()));
event.consume();
});

View file

@ -209,6 +209,7 @@ public abstract class StoreEntryComp extends SimpleComp {
action.execute();
});
});
button.accessibleText(actionProvider.getName(wrapper.getEntry().ref()).getValue());
button.apply(new FancyTooltipAugment<>(
actionProvider.getName(wrapper.getEntry().ref())));
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {

View file

@ -22,7 +22,7 @@ public class StoreEntryListComp extends SimpleComp {
StoreViewState.get().getCurrentTopLevelSection().getAllChildren(),
(StoreSection e) -> {
var custom = StoreSection.customSection(e, true).hgrow();
return new HorizontalComp(List.of(Comp.hspacer(10), custom, Comp.hspacer(10)))
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
.styleClass("top");
})
.apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0)));

View file

@ -3,12 +3,17 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
@ -16,11 +21,25 @@ import javafx.scene.control.MenuButton;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import org.kordamp.ikonli.javafx.FontIcon;
public class StoreEntryListStatusComp extends SimpleComp {
private final Property<StoreSortMode> sortMode;
public StoreEntryListStatusComp() {
this.sortMode = new SimpleObjectProperty<>();
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
sortMode.unbind();
sortMode.bindBidirectional(val.getSortMode());
});
}
private Region createGroupListHeader() {
var label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
@ -44,12 +63,11 @@ public class StoreEntryListStatusComp extends SimpleComp {
StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, all);
var spacer = new Region();
var topBar = new HBox(label, spacer, count.createRegion());
AppFont.setSize(topBar, 1);
var c = count.createRegion();
var topBar = new HBox(label, c, Comp.hspacer().createRegion(), createDateSortButton().createRegion(), Comp.hspacer(2).createRegion(), createAlphabeticalSortButton().createRegion());
AppFont.setSize(label, 3);
AppFont.setSize(c, 3);
topBar.setAlignment(Pos.CENTER);
HBox.setHgrow(spacer, Priority.ALWAYS);
topBar.getStyleClass().add("top");
return topBar;
}
@ -65,25 +83,117 @@ public class StoreEntryListStatusComp extends SimpleComp {
filter.shortcut(new KeyCodeCombination(KeyCode.F, KeyCombination.SHORTCUT_DOWN), s -> {
s.getText().requestFocus();
});
var r = new StackPane(filter.createRegion());
r.setAlignment(Pos.CENTER);
r.getStyleClass().add("filter-bar");
AppFont.medium(r);
return r;
filter.apply(struc -> struc.get().sceneProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
struc.getText().requestFocus();
}
}));
var f = filter.createRegion();
var hbox = new HBox(createButtons(), f);
hbox.setSpacing(8);
hbox.setAlignment(Pos.CENTER);
HBox.setHgrow(f, Priority.ALWAYS);
f.getStyleClass().add("filter-bar");
AppFont.medium(hbox);
return hbox;
}
private Region createButtons() {
var menu = new MenuButton(AppI18n.get("addConnections"), new FontIcon("mdi2p-plus-thick"));
menu.setAlignment(Pos.CENTER);
menu.setTextAlignment(TextAlignment.CENTER);
AppFont.medium(menu);
GrowAugment.create(true, false).augment(menu);
StoreCreationMenu.addButtons(menu);
menu.setOpacity(0.85);
menu.setMinWidth(Region.USE_PREF_SIZE);
return menu;
}
private Comp<?> createAlphabeticalSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
return "mdi2s-sort-alphabetical-descending";
}
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return "mdi2s-sort-alphabetical-ascending";
}
return "mdi2s-sort-alphabetical-descending";
},
sortMode);
var alphabetical = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_DESC);
} else if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
} else {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
}
});
alphabetical.apply(alphabeticalR -> {
alphabeticalR
.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC
|| sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
alphabetical.accessibleTextKey("sortAlphabetical");
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
return alphabetical;
}
private Comp<?> createDateSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
return "mdi2s-sort-clock-ascending-outline";
}
if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
return "mdi2s-sort-clock-descending-outline";
}
return "mdi2s-sort-clock-ascending-outline";
},
sortMode);
var date = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
sortMode.setValue(StoreSortMode.DATE_DESC);
} else if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
sortMode.setValue(StoreSortMode.DATE_ASC);
} else {
sortMode.setValue(StoreSortMode.DATE_ASC);
}
});
date.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC
|| sortMode.getValue() == StoreSortMode.DATE_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
date.accessibleTextKey("sortLastUsed");
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN));
return date;
}
@Override
public Region createSimple() {
var bar = new VBox(createGroupListHeader(), createGroupListFilter(), createButtons());
var bar = new VBox(createGroupListHeader(), createGroupListFilter());
bar.setFillWidth(true);
bar.getStyleClass().add("bar");
bar.getStyleClass().add("store-header-bar");

View file

@ -190,6 +190,10 @@ public class StoreEntryWrapper {
}
public void executeDefaultAction() throws Exception {
if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return;
}
if (getEntry().getValidity() == DataStoreEntry.Validity.INCOMPLETE) {
editDialog();
return;

View file

@ -1,12 +1,12 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.SideSplitPaneComp;
import io.xpipe.app.core.AppActionLinkDetector;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
public class StoreLayoutComp extends SimpleComp {
@ -19,14 +19,13 @@ public class StoreLayoutComp extends SimpleComp {
@Override
protected Region createSimple() {
var listComp = new StoreEntryListComp().apply(GrowAugment.create(false, true));
var r = new BorderPane();
var listR = listComp.createRegion();
var groupHeader = new StoreSidebarComp().createRegion();
r.setLeft(groupHeader);
r.setCenter(listR);
r.getStyleClass().add("layout");
return r;
var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth(
AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> {
AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble);
}).createStructure();
struc.getLeft().setMinWidth(260);
struc.getLeft().setMaxWidth(500);
struc.get().getStyleClass().add("store-layout");
return struc.get();
}
}

View file

@ -53,7 +53,9 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setMinWidth(30))
.apply(struc -> struc.get().setPrefWidth(30))
.focusTraversable()
.accessibleText("Expand")
.accessibleText(Bindings.createStringBinding(() -> {
return "Expand " + section.getWrapper().getName().getValue();
}, section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getShownChildren()).isEqualTo(0)))
.grow(false, true)
@ -85,7 +87,7 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
return new VerticalComp(List.of(
new HorizontalComp(topEntryList)
.apply(struc -> struc.get().setFillHeight(true)),
Comp.separator().visible(expanded),
Comp.separator().hide(BindingsHelper.persist(expanded.not())),
new HorizontalComp(List.of(content))
.styleClass("content")
.apply(struc -> struc.get().setFillHeight(true))

View file

@ -74,7 +74,9 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
.apply(struc -> struc.get().setMinWidth(20))
.apply(struc -> struc.get().setPrefWidth(20))
.focusTraversable()
.accessibleText("Expand")
.accessibleText(Bindings.createStringBinding(() -> {
return "Expand " + section.getWrapper().getName().getValue();
}, section.getWrapper().getName()))
.disable(BindingsHelper.persist(
Bindings.size(section.getAllChildren()).isEqualTo(0)))
.grow(false, true)

View file

@ -13,11 +13,10 @@ public class StoreSidebarComp extends SimpleComp {
@Override
protected Region createSimple() {
var sideBar = new VerticalComp(List.of(
new StoreEntryListStatusComp(),
new StoreSortComp(),
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()),
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()),
Comp.of(() -> new Region()).styleClass("bar").styleClass("filler-bar").vgrow()));
new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"),
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()).styleClass("color-box").styleClass("gray"),
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()).styleClass("color-box").styleClass("gray"),
Comp.of(() -> new Region()).styleClass("bar").styleClass("color-box").styleClass("gray").styleClass("filler-bar").vgrow()));
sideBar.apply(struc -> struc.get().setFillWidth(true));
sideBar.styleClass("sidebar");
sideBar.prefWidth(240);

View file

@ -1,122 +0,0 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.IconButtonComp;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Region;
import java.util.List;
public class StoreSortComp extends SimpleComp {
private final Property<StoreSortMode> sortMode;
public StoreSortComp() {
this.sortMode = new SimpleObjectProperty<>();
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
sortMode.unbind();
sortMode.bindBidirectional(val.getSortMode());
});
}
private Comp<?> createAlphabeticalSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
return "mdi2s-sort-alphabetical-descending";
}
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return "mdi2s-sort-alphabetical-ascending";
}
return "mdi2s-sort-alphabetical-descending";
},
sortMode);
var alphabetical = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_DESC);
} else if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
} else {
sortMode.setValue(StoreSortMode.ALPHABETICAL_ASC);
}
});
alphabetical.apply(alphabeticalR -> {
alphabeticalR
.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.ALPHABETICAL_ASC
|| sortMode.getValue() == StoreSortMode.ALPHABETICAL_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
alphabetical.apply(new FancyTooltipAugment<>("sortAlphabetical"));
alphabetical.shortcut(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
return alphabetical;
}
private Comp<?> createDateSortButton() {
var icon = Bindings.createStringBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
return "mdi2s-sort-clock-ascending-outline";
}
if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
return "mdi2s-sort-clock-descending-outline";
}
return "mdi2s-sort-clock-ascending-outline";
},
sortMode);
var date = new IconButtonComp(icon, () -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC) {
sortMode.setValue(StoreSortMode.DATE_DESC);
} else if (sortMode.getValue() == StoreSortMode.DATE_DESC) {
sortMode.setValue(StoreSortMode.DATE_ASC);
} else {
sortMode.setValue(StoreSortMode.DATE_ASC);
}
});
date.apply(dateR -> {
dateR.get()
.opacityProperty()
.bind(Bindings.createDoubleBinding(
() -> {
if (sortMode.getValue() == StoreSortMode.DATE_ASC
|| sortMode.getValue() == StoreSortMode.DATE_DESC) {
return 1.0;
}
return 0.4;
},
sortMode));
});
date.apply(new FancyTooltipAugment<>("sortLastUsed"));
date.shortcut(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN));
return date;
}
private Comp<?> createSortButtonBar() {
return new HorizontalComp(List.of(createDateSortButton(), createAlphabeticalSortButton())).apply(struc -> {
struc.get().setMinHeight(40);
struc.get().setPrefHeight(40);
struc.get().setMaxHeight(40);
}).styleClass("bar").styleClass("store-sort-bar");
}
@Override
protected Region createSimple() {
return createSortButtonBar().createRegion();
}
}

View file

@ -50,7 +50,7 @@ public class AppCache {
var path = getPath(key);
if (Files.exists(path)) {
try {
var tree = JsonConfigHelper.readConfig(path);
var tree = JsonConfigHelper.readRaw(path);
if (tree.isMissingNode()) {
return notPresent.get();
}

View file

@ -2,6 +2,8 @@ package io.xpipe.app.core;
import io.xpipe.core.charsetter.Charsetter;
import io.xpipe.core.charsetter.StreamCharset;
import io.xpipe.core.util.FailableConsumer;
import io.xpipe.core.util.FailableSupplier;
import org.apache.commons.io.ByteOrderMark;
import org.apache.commons.io.input.BOMInputStream;
@ -17,7 +19,7 @@ public class AppCharsetter extends Charsetter {
Charsetter.INSTANCE = new AppCharsetter();
}
public Result read(FailableSupplier<InputStream, Exception> in, FailableConsumer<InputStreamReader, Exception> con)
public Result read(FailableSupplier<InputStream> in, FailableConsumer<InputStreamReader, Exception> con)
throws Exception {
checkInit();

View file

@ -47,7 +47,7 @@ public class AppExtensionManager {
}
if (load) {
INSTANCE.addNativeLibrariesToPath();
// INSTANCE.addNativeLibrariesToPath();
try {
XPipeServiceProviders.load(INSTANCE.extendedLayer);
MessageExchangeImpls.loadAll();

View file

@ -5,18 +5,13 @@ import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.MarkdownHelper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Modality;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.function.UnaryOperator;
@ -39,14 +34,14 @@ public class AppGreetings {
return tp;
}
private static TitledPane createTos() {
private static TitledPane createEula() {
var tp = new TitledPane();
tp.setExpanded(false);
tp.setText(AppI18n.get("tos"));
tp.setText(AppI18n.get("eula"));
tp.setAlignment(Pos.CENTER_LEFT);
AppFont.normal(tp);
AppResources.with(AppResources.XPIPE_MODULE, "misc/tos.md", file -> {
AppResources.with(AppResources.XPIPE_MODULE, "misc/eula.md", file -> {
var md = Files.readString(file);
var markdown = new MarkdownComp(md, UnaryOperator.identity()).createRegion();
tp.setContent(markdown);
@ -67,7 +62,7 @@ public class AppGreetings {
alert.setAlertType(Alert.AlertType.NONE);
alert.initModality(Modality.APPLICATION_MODAL);
var content = List.of(createIntroduction(), createTos());
var content = List.of(createIntroduction(), createEula());
var accordion = new Accordion(content.toArray(TitledPane[]::new));
accordion.setExpandedPane(content.get(0));
accordion.expandedPaneProperty().addListener((observable, oldValue, newValue) -> {
@ -98,26 +93,6 @@ public class AppGreetings {
alert.getDialogPane().setContent(layout);
{
var view = new ButtonType(AppI18n.get("print"), ButtonBar.ButtonData.OTHER);
alert.getButtonTypes().add(view);
Button button = (Button) alert.getDialogPane().lookupButton(view);
button.visibleProperty().bind(read);
button.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
try {
var temp = Files.createTempFile("tos", ".html");
AppResources.with(AppResources.XPIPE_MODULE, "misc/tos.md", file -> {
Files.writeString(
temp,
MarkdownHelper.toHtml(Files.readString(file), UnaryOperator.identity()));
});
Hyperlinks.open(temp.toUri().toString());
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
}
event.consume();
});
}
{
var buttonType = new ButtonType(AppI18n.get("confirm"), ButtonBar.ButtonData.OK_DONE);
alert.getButtonTypes().add(buttonType);

View file

@ -10,7 +10,10 @@ import io.xpipe.app.util.LicenseProvider;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List;
@ -18,6 +21,15 @@ import java.util.List;
@Getter
public class AppLayoutModel {
@Data
@Builder
@Jacksonized
public static class SavedState {
double sidebarWidth;
double browserConnectionsWidth;
}
private static AppLayoutModel INSTANCE;
public static AppLayoutModel get() {
@ -25,13 +37,22 @@ public class AppLayoutModel {
}
public static void init() {
INSTANCE = new AppLayoutModel();
var state = AppCache.get("layoutState", SavedState.class, () -> new SavedState(260, 300));
INSTANCE = new AppLayoutModel(state);
}
public static void reset() {
AppCache.update("layoutState", INSTANCE.savedState);
INSTANCE = null;
}
@Getter
private final SavedState savedState;
private final List<Entry> entries;
private final Property<Entry> selected;
public AppLayoutModel() {
public AppLayoutModel(SavedState savedState) {
this.savedState = savedState;
this.entries = createEntryList();
this.selected = new SimpleObjectProperty<>(entries.get(1));
}

View file

@ -182,7 +182,7 @@ public class AppMainWindow {
}
private void saveState() {
if (!AppPrefs.get().saveWindowLocation.get()) {
if (!AppPrefs.get().saveWindowLocation().get()) {
return;
}
@ -201,7 +201,7 @@ public class AppMainWindow {
}
private WindowState loadState() {
if (!AppPrefs.get().saveWindowLocation.get()) {
if (!AppPrefs.get().saveWindowLocation().get()) {
return null;
}
@ -230,6 +230,9 @@ public class AppMainWindow {
}
public void initialize() {
stage.setMinWidth(550);
stage.setMinHeight(400);
initializeWindow();
setupListeners();
windowActive.set(true);

View file

@ -13,23 +13,49 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class AppResources {
public static final String XPIPE_MODULE = "io.xpipe.app";
private static ModuleFileSystem openFileSystem(String module) throws IOException {
private static final Map<String, ModuleFileSystem> fileSystems = new ConcurrentHashMap<>();
public static void reset() {
fileSystems.forEach((s, moduleFileSystem) -> {
try {
moduleFileSystem.close();
} catch (IOException e) {
ErrorEvent.fromThrowable(e).handle();
}
});
fileSystems.clear();
}
private static ModuleFileSystem openFileSystemIfNeeded(String module) throws IOException {
var layer = AppExtensionManager.getInstance() != null
? AppExtensionManager.getInstance().getExtendedLayer()
: null;
// Only cache file systems with extended layer
if (layer != null && fileSystems.containsKey(module)) {
return fileSystems.get(module);
}
if (layer == null) {
layer = ModuleLayer.boot();
}
return (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
var fs = (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
if (AppExtensionManager.getInstance() != null) {
fileSystems.put(module, fs);
}
return fs;
}
public static Optional<URL> getResourceURL(String module, String file) {
try (var fs = openFileSystem(module)) {
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
var url = f.getWrappedPath().toUri().toURL();
return Optional.of(url);
@ -64,7 +90,8 @@ public class AppResources {
private static void withResource(String module, String file, FailableConsumer<Path, IOException> con) {
var path = module.startsWith("io.xpipe") ? module.replace('.', '/') + "/resources/" + file : file;
try (var fs = openFileSystem(module)) {
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(path);
con.accept(f);
} catch (IOException e) {
@ -73,7 +100,8 @@ public class AppResources {
}
private static boolean withLocalDevResource(String module, String file, FailableConsumer<Path, IOException> con) {
try (var fs = openFileSystem(module)) {
try {
var fs = openFileSystemIfNeeded(module);
var url = fs.getPath("").getWrappedPath().toUri().toURL();
if (!url.getProtocol().equals("jar")) {
return false;

View file

@ -45,8 +45,9 @@ public class AppSocketServer {
}
public static void init() {
int port = -1;
try {
var port = BeaconConfig.getUsedPort();
port = BeaconConfig.getUsedPort();
INSTANCE = new AppSocketServer(port);
INSTANCE.createSocketListener();
@ -56,7 +57,7 @@ public class AppSocketServer {
.handle();
} catch (Exception ex) {
// Not terminal!
ErrorEvent.fromThrowable(ex).build().handle();
ErrorEvent.fromThrowable(ex).description("Unable to start local socket server on port " + port).build().handle();
}
}

View file

@ -35,6 +35,7 @@ public class AppTheme {
private static final PseudoClass DARK = PseudoClass.getPseudoClass("dark");
private static final PseudoClass PRETTY = PseudoClass.getPseudoClass("pretty");
private static final PseudoClass PERFORMANCE = PseudoClass.getPseudoClass("performance");
private static boolean init;
public static void initThemeHandlers(Window stage) {
if (AppPrefs.get() == null) {
@ -60,6 +61,10 @@ public class AppTheme {
}
public static void init() {
if (init) {
return;
}
if (AppPrefs.get() == null) {
Theme.getDefaultLightTheme().apply();
return;
@ -97,6 +102,8 @@ public class AppTheme {
AppPrefs.get().theme.addListener((c, o, n) -> {
changeTheme(n);
});
init = true;
}
private static void setDefault(boolean dark) {

View file

@ -68,6 +68,7 @@ public class BaseMode extends OperationMode {
StoreViewState.reset();
DataStorage.reset();
AppPrefs.reset();
AppResources.reset();
AppExtensionManager.reset();
AppDataLock.unlock();
// Shut down socket server last to keep a non-daemon thread running

View file

@ -6,7 +6,6 @@ import io.xpipe.app.core.AppMainWindow;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.update.CommercializationAlert;
import io.xpipe.app.update.UpdateChangelogAlert;
import io.xpipe.app.util.UnlockAlert;
import javafx.stage.Stage;
@ -24,7 +23,6 @@ public class GuiMode extends PlatformMode {
UnlockAlert.showIfNeeded();
UpdateChangelogAlert.showIfNeeded();
CommercializationAlert.showIfNeeded();
AppGreetings.showIfNeeded();
TrackEvent.info("mode", "Waiting for window setup completion ...");

View file

@ -62,6 +62,7 @@ public abstract class PlatformMode extends OperationMode {
TrackEvent.info("mode", "Shutting down platform components");
onSwitchFrom();
StoreViewState.reset();
AppLayoutModel.reset();
PlatformState.teardown();
TrackEvent.info("mode", "Platform shutdown finished");
BACKGROUND.finalTeardown();

View file

@ -22,6 +22,10 @@ import java.util.List;
public interface DataStoreProvider {
default boolean editByDefault() {
return false;
}
default boolean canMoveCategories() {
return true;
}

View file

@ -1,6 +1,7 @@
package io.xpipe.app.fxcomps;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.augment.Augment;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.Shortcuts;
@ -83,6 +84,15 @@ public abstract class Comp<S extends CompStructure<?>> {
return apply(struc -> struc.get().setPrefHeight(height));
}
public Comp<S> maxWidth(int width) {
return apply(struc -> struc.get().setMaxWidth(width));
}
public Comp<S> maxHeight(int height) {
return apply(struc -> struc.get().setMaxHeight(height));
}
public Comp<S> hgrow() {
return apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
}
@ -130,10 +140,18 @@ public abstract class Comp<S extends CompStructure<?>> {
return apply(struc -> struc.get().getStyleClass().add(styleClass));
}
public Comp<S> accessibleText(ObservableValue<String> text) {
return apply(struc -> struc.get().accessibleTextProperty().bind(text));
}
public Comp<S> accessibleText(String text) {
return apply(struc -> struc.get().setAccessibleText(text));
}
public Comp<S> accessibleTextKey(String key) {
return apply(struc -> struc.get().accessibleTextProperty().bind(AppI18n.observable(key)));
}
public Comp<S> grow(boolean width, boolean height) {
return apply(GrowAugment.create(width, height));
}

View file

@ -6,10 +6,8 @@ import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.store.*;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
@ -23,7 +21,6 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
@ -32,7 +29,6 @@ import lombok.RequiredArgsConstructor;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
@RequiredArgsConstructor
@ -128,6 +124,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
StoreCreationMenu.addButtons(m);
return m;
})
.accessibleTextKey("addConnection")
.padding(new Insets(-2))
.styleClass(Styles.RIGHT_PILL)
.grow(false, true);
@ -155,26 +152,6 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
return popover;
}
protected Region createGraphic(T s) {
var provider = DataStoreProviders.byStore(s);
var imgView = PrettyImageHelper.ofFixedSquare(provider.getDisplayIconFileName(s), 16)
.createRegion();
var name = DataStorage.get().getUsableStores().stream()
.filter(e -> e.equals(s))
.findAny()
.flatMap(store -> {
if (mode == Mode.PROXY && ShellStore.isLocal(store.asNeeded())) {
return Optional.of(AppI18n.get("none"));
}
return DataStorage.get().getStoreDisplayName(store);
})
.orElse(AppI18n.get("unknown"));
return new Label(name, imgView);
}
private String toName(DataStoreEntry entry) {
if (entry == null) {
return null;
@ -226,7 +203,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
})
.styleClass("choice-comp");
var r = button.grow(true, false).createRegion();
var r = button.grow(true, false).accessibleText("Select connection").createRegion();
var icon = new FontIcon("mdal-keyboard_arrow_down");
icon.setDisable(true);
icon.setPickOnBounds(false);

View file

@ -28,7 +28,7 @@ public class FilterComp extends Comp<FilterComp.Structure> {
@Override
public Structure createBase() {
var fi = new FontIcon("mdi2m-magnify");
var bgLabel = new Label("Search ...", fi);
var bgLabel = new Label("Search", fi);
bgLabel.getStyleClass().add("filter-background");
var filter = new TextField();
filter.setAccessibleText("Filter");

View file

@ -97,6 +97,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
extendedDescription.getStyleClass().add(Styles.ACCENT);
extendedDescription.setPadding(new Insets(0, 6, 0, 6));
extendedDescription.getStyleClass().add("long-description");
extendedDescription.setAccessibleText("Help");
AppFont.header(extendedDescription);
extendedDescription.setOnAction(e -> popover.show(extendedDescription));

View file

@ -1,5 +1,6 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.CountComp;
import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
@ -41,68 +42,52 @@ public class StoreCategoryComp extends SimpleComp {
@Override
protected Region createSimple() {
var i = Bindings.createStringBinding(
() -> {
if (!DataStorage.get().supportsSharing()) {
return "mdal-keyboard_arrow_right";
}
var i = Bindings.createStringBinding(() -> {
if (!DataStorage.get().supportsSharing()) {
return "mdal-keyboard_arrow_right";
}
return category.getShare().getValue() ?
"mdi2a-account-convert" : "mdi2a-account-cancel";
},
category.getShare());
return category.getShare().getValue() ? "mdi2a-account-convert" : "mdi2a-account-cancel";
}, category.getShare());
var icon = new IconButtonComp(i).apply(struc -> AppFont.small(struc.get())).apply(struc -> {
struc.get().setAlignment(Pos.CENTER);
struc.get().setPadding(new Insets(0, 0, 6, 0));
struc.get().setFocusTraversable(false);
});
var name = new LazyTextFieldComp(category.nameProperty())
.apply(struc -> {
struc.get().prefWidthProperty().unbind();
struc.get().setPrefWidth(150);
struc.getTextField().minWidthProperty().bind(struc.get().widthProperty());
})
.styleClass("name")
.createRegion();
var name = new LazyTextFieldComp(category.nameProperty()).apply(struc -> {
struc.get().prefWidthProperty().unbind();
struc.get().setPrefWidth(150);
struc.getTextField().minWidthProperty().bind(struc.get().widthProperty());
}).styleClass("name").createRegion();
var showing = new SimpleBooleanProperty();
var settings = new IconButtonComp("mdomz-settings")
.styleClass("settings")
.apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, () -> {
var settings = new IconButtonComp("mdomz-settings").styleClass("settings").apply(
new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, () -> {
var cm = createContextMenu(name);
showing.bind(cm.showingProperty());
return cm;
}));
var shownList = BindingsHelper.filteredContentBinding(
category.getContainedEntries(),
storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(
StoreViewState.get().getFilterString().getValue());
},
StoreViewState.get().getFilterString());
var shownList = BindingsHelper.filteredContentBinding(category.getContainedEntries(), storeEntryWrapper -> {
return storeEntryWrapper.shouldShow(StoreViewState.get().getFilterString().getValue());
}, StoreViewState.get().getFilterString());
var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")");
var hover = new SimpleBooleanProperty();
var h = new HorizontalComp(List.of(
icon,
Comp.hspacer(4),
Comp.of(() -> name),
Comp.hspacer(),
count.hide(BindingsHelper.persist(hover.or(showing))),
settings.hide(BindingsHelper.persist(hover.not().and(showing.not())))));
h.apply(struc -> hover.bind(struc.get().hoverProperty()));
h.apply(struc -> struc.get().setOnMouseClicked(event -> {
category.select();
event.consume();
}));
h.apply(new ContextMenuAugment<>(
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name)));
var focus = new SimpleBooleanProperty();
var h = new HorizontalComp(
List.of(icon, Comp.hspacer(4), Comp.of(() -> name), Comp.hspacer(), count.hide(BindingsHelper.persist(hover.or(showing).or(focus))),
settings.hide(BindingsHelper.persist(hover.not().and(showing.not()).and(focus.not())))));
h.apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name)));
h.padding(new Insets(0, 10, 0, (category.getDepth() * 10)));
h.styleClass("category-button");
var l = category.getChildren()
.sorted(Comparator.comparing(
storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT)));
var categoryButton = new ButtonComp(null, h.createRegion(), category::select).styleClass("category-button").apply(
struc -> hover.bind(struc.get().hoverProperty())).apply(struc -> focus.bind(struc.get().focusedProperty())).accessibleText(
category.nameProperty()).grow(true, false);
var l = category.getChildren().sorted(Comparator.comparing(storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT)));
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper));
var emptyBinding = Bindings.isEmpty(category.getChildren());
var v = new VerticalComp(List.of(h, Comp.separator().hide(emptyBinding), Comp.vspacer(5).hide(emptyBinding), children.hide(emptyBinding)));
var v = new VerticalComp(
List.of(categoryButton, children.hide(emptyBinding)));
v.styleClass("category");
v.apply(struc -> {
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {
@ -119,9 +104,7 @@ public class StoreCategoryComp extends SimpleComp {
var newCategory = new MenuItem(AppI18n.get("newCategory"), new FontIcon("mdi2p-plus-thick"));
newCategory.setOnAction(event -> {
DataStorage.get()
.addStoreCategory(
DataStoreCategory.createNew(category.getCategory().getUuid(), "New category"));
DataStorage.get().addStoreCategory(DataStoreCategory.createNew(category.getCategory().getUuid(), "New category"));
});
contextMenu.getItems().add(newCategory);
@ -132,14 +115,14 @@ public class StoreCategoryComp extends SimpleComp {
} else {
return AppI18n.get("share");
}
},category.getShare()));
}, category.getShare()));
share.graphicProperty().bind(Bindings.createObjectBinding(() -> {
if (category.getShare().getValue()) {
return new FontIcon("mdi2b-block-helper");
} else {
return new FontIcon("mdi2s-share");
}
},category.getShare()));
}, category.getShare()));
share.setOnAction(event -> {
category.getShare().setValue(!category.getShare().getValue());
});

View file

@ -63,8 +63,8 @@ public class AboutComp extends Comp<CompStructure<?>> {
.grow(true, false),
null)
.addComp(
new TileButtonComp("termsOfService", "termsOfServiceDescription", "mdi2c-card-text-outline", e -> {
Hyperlinks.open(Hyperlinks.TOS);
new TileButtonComp("eula", "eulaDescription", "mdi2c-card-text-outline", e -> {
Hyperlinks.open(Hyperlinks.EULA);
e.consume();
})
.grow(true, false),

View file

@ -112,8 +112,7 @@ public class AppPrefs {
return connectionTimeOut;
}
private final BooleanProperty saveWindowLocationInternal = typed(new SimpleBooleanProperty(false), Boolean.class);
public final ReadOnlyBooleanProperty saveWindowLocation = saveWindowLocationInternal;
private final BooleanProperty saveWindowLocation = typed(new SimpleBooleanProperty(true), Boolean.class);
// External terminal
// =================
@ -336,6 +335,10 @@ public class AppPrefs {
return windowOpacity;
}
public ObservableBooleanValue saveWindowLocation() {
return saveWindowLocation;
}
public ObservableBooleanValue developerDisableUpdateVersionCheck() {
return developerDisableUpdateVersionCheckEffective;
}
@ -591,7 +594,7 @@ public class AppPrefs {
Setting.of("useSystemFont", BooleanField.ofBooleanType(useSystemFontInternal).render(() -> new CustomToggleControl()), useSystemFontInternal),
Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax),
Setting.of("language", languageControl, languageInternal)),
Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocationInternal).render(() -> new CustomToggleControl()), saveWindowLocationInternal))),
Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocation).render(() -> new CustomToggleControl()), saveWindowLocation))),
Category.of(
"connections",
Group.of(

View file

@ -33,7 +33,7 @@ public class JsonStorageHandler implements StorageHandler {
private JsonNode getContent(String key) {
if (content == null) {
content = (ObjectNode) JsonConfigHelper.readConfig(file);
content = JsonConfigHelper.readConfigObject(file);
}
return content.get(key);
}

View file

@ -9,7 +9,6 @@ import com.dlsc.preferencesfx.model.Group;
import com.dlsc.preferencesfx.model.Setting;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.LockChangeAlert;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.binding.Bindings;
@ -58,7 +57,7 @@ public class VaultCategory extends AppPrefsCategory {
@SneakyThrows
public Category create() {
var pro = LicenseProvider.get().getFeature("gitVault").isSupported();
var pro = true;
BooleanField enable = BooleanField.ofBooleanType(prefs.enableGitStorage)
.editable(pro)
.render(() -> {

View file

@ -33,7 +33,6 @@ public abstract class DataStorage {
public static final UUID LOCAL_ID = UUID.fromString("f0ec68aa-63f5-405c-b178-9a4454556d6b");
private static final String PERSIST_PROP = "io.xpipe.storage.persist";
private static final String IMMUTABLE_PROP = "io.xpipe.storage.immutable";
private static DataStorage INSTANCE;
protected final Path dir;
@ -41,8 +40,10 @@ public abstract class DataStorage {
@Getter
protected final List<DataStoreCategory> storeCategories;
protected final Map<DataStoreEntry, DataStoreEntry> storeEntries;
@Getter
protected final Set<DataStoreEntry> storeEntries;
protected final Set<DataStoreEntry> storeEntriesSet;
@Getter
@Setter
@ -53,7 +54,8 @@ public abstract class DataStorage {
public DataStorage() {
this.dir = AppPrefs.get().storageDirectory().getValue();
this.storeEntries = Collections.newSetFromMap(new ConcurrentHashMap<>());
this.storeEntries = new ConcurrentHashMap<>();
this.storeEntriesSet = storeEntries.keySet();
this.storeCategories = new CopyOnWriteArrayList<>();
}
@ -138,7 +140,7 @@ public abstract class DataStorage {
var changed = new AtomicBoolean(false);
do {
changed.set(false);
storeEntries.forEach(dataStoreEntry -> {
storeEntries.keySet().forEach(dataStoreEntry -> {
if (makeValid ? dataStoreEntry.tryMakeValid() : dataStoreEntry.tryMakeInvalid()) {
changed.set(true);
}
@ -241,7 +243,7 @@ public abstract class DataStorage {
public void deleteChildren(DataStoreEntry e) {
var c = getDeepStoreChildren(e);
c.forEach(entry -> entry.finalizeEntry());
this.storeEntries.removeAll(c);
this.storeEntriesSet.removeAll(c);
this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new)));
refreshValidities(false);
saveAsync();
@ -257,7 +259,7 @@ public abstract class DataStorage {
.toList();
toDelete.forEach(entry -> entry.finalizeEntry());
this.storeEntries.removeAll(toDelete);
this.storeEntriesSet.removeAll(toDelete);
this.listeners.forEach(l -> l.onStoreRemove(toDelete.toArray(DataStoreEntry[]::new)));
refreshValidities(false);
saveAsync();
@ -286,8 +288,9 @@ public abstract class DataStorage {
}
public DataStoreEntry addStoreEntryIfNotPresent(@NonNull DataStoreEntry e) {
if (storeEntries.contains(e)) {
return e;
var found = storeEntries.get(e);
if (found != null) {
return found;
}
var byId = getStoreEntryIfPresent(e.getUuid()).orElse(null);
@ -295,6 +298,10 @@ public abstract class DataStorage {
return byId;
}
if (getStoreCategoryIfPresent(e.getCategoryUuid()).isEmpty()) {
e.setCategoryUuid(DEFAULT_CATEGORY_UUID);
}
var syntheticParent = getSyntheticParent(e);
if (syntheticParent.isPresent()) {
addStoreEntryIfNotPresent(syntheticParent.get());
@ -307,7 +314,7 @@ public abstract class DataStorage {
}
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
this.storeEntries.put(e, e);
displayParent.ifPresent(p -> {
p.setChildrenCache(null);
});
@ -321,7 +328,7 @@ public abstract class DataStorage {
public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) {
for (DataStoreEntry e : es) {
if (storeEntries.contains(e) || getStoreEntryIfPresent(e.getStore()).isPresent()) {
if (storeEntriesSet.contains(e) || getStoreEntryIfPresent(e.getStore()).isPresent()) {
return;
}
@ -337,7 +344,7 @@ public abstract class DataStorage {
}
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
this.storeEntries.put(e, e);
displayParent.ifPresent(p -> {
p.setChildrenCache(null);
});
@ -379,7 +386,7 @@ public abstract class DataStorage {
return;
}
storeEntries.forEach(entry -> {
storeEntriesSet.forEach(entry -> {
if (entry.getCategoryUuid().equals(cat.getUuid())) {
entry.setCategoryUuid(DEFAULT_CATEGORY_UUID);
}
@ -459,7 +466,7 @@ public abstract class DataStorage {
try {
var provider = entry.getProvider();
return Optional.ofNullable(provider.getDisplayParent(entry))
.filter(dataStoreEntry -> storeEntries.contains(dataStoreEntry));
.filter(dataStoreEntry -> storeEntriesSet.contains(dataStoreEntry));
} catch (Exception ex) {
return Optional.empty();
}
@ -559,7 +566,7 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
return storeEntries.stream()
return storeEntriesSet.stream()
.filter(n -> n.getStore() == store
|| (n.getStore() != null
&& Objects.equals(store.getClass(), n.getStore().getClass())
@ -592,7 +599,7 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull String name) {
return storeEntries.stream()
return storeEntriesSet.stream()
.filter(n -> n.getName().equalsIgnoreCase(name))
.findFirst();
}
@ -614,7 +621,11 @@ public abstract class DataStorage {
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(UUID id) {
return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
return storeEntriesSet.stream().filter(e -> e.getUuid().equals(id)).findAny();
}
public Set<DataStoreEntry> getStoreEntries() {
return storeEntriesSet;
}
public DataStoreEntry getOrCreateNewSyntheticEntry(DataStoreEntry parent, String name, DataStore store) {

View file

@ -99,6 +99,27 @@ public class DataStoreEntry extends StorageElement {
this.storePersistentStateNode = storePersistentState;
}
private DataStoreEntry(
Path directory,
UUID uuid,
UUID categoryUuid,
String name,
Instant lastUsed,
Instant lastModified,
DataStore store
) {
super(directory, uuid, name, lastUsed, lastModified, false);
this.categoryUuid = categoryUuid;
this.store = store;
this.storeNode = null;
this.validity = Validity.COMPLETE;
this.configuration = Configuration.defaultConfiguration();
this.expanded = false;
this.color = null;
this.provider = null;
this.storePersistentStateNode = null;
}
@Override
public boolean equals(Object o) {
return o == this || (o instanceof DataStoreEntry e && e.getUuid().equals(getUuid()));
@ -114,6 +135,18 @@ public class DataStoreEntry extends StorageElement {
return getName();
}
public static DataStoreEntry createTempWrapper(@NonNull DataStore store) {
return new DataStoreEntry(
null,
UUID.randomUUID(),
DataStorage.get().getSelectedCategory().getUuid(),
UUID.randomUUID().toString(),
Instant.now(),
Instant.now(),
store
);
}
public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) {
return createNew(UUID.randomUUID(), DataStorage.get().getSelectedCategory().getUuid(), name, store);
}
@ -121,6 +154,8 @@ public class DataStoreEntry extends StorageElement {
@SneakyThrows
public static DataStoreEntry createNew(
@NonNull UUID uuid, @NonNull UUID categoryUuid, @NonNull String name, @NonNull DataStore store) {
var node = DataStorageWriter.storeToNode(store);
var validity = DataStorageParser.storeFromNode(node) == null ? Validity.LOAD_FAILED : store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE;
var entry = new DataStoreEntry(
null,
uuid,
@ -128,9 +163,9 @@ public class DataStoreEntry extends StorageElement {
name,
Instant.now(),
Instant.now(),
DataStorageWriter.storeToNode(store),
node,
true,
store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE,
validity,
Configuration.defaultConfiguration(),
null,
false, null

View file

@ -26,6 +26,8 @@ public class StandardStorage extends DataStorage {
@Getter
private final GitStorageHandler gitStorageHandler;
private boolean loaded;
StandardStorage() {
this.gitStorageHandler = LicenseProvider.get().createStorageHandler();
this.gitStorageHandler.init(dir);
@ -109,7 +111,6 @@ public class StandardStorage extends DataStorage {
public synchronized void load() {
this.gitStorageHandler.prepareForLoad();
var newSession = isNewSession();
var storesDir = getStoresDir();
var categoriesDir = getCategoriesDir();
@ -173,6 +174,12 @@ public class StandardStorage extends DataStorage {
storeCategories.add(cat);
}
if (getStoreCategoryIfPresent(CUSTOM_SCRIPTS_CATEGORY_UUID).isEmpty()) {
var cat = DataStoreCategory.createNew(ALL_SCRIPTS_CATEGORY_UUID, CUSTOM_SCRIPTS_CATEGORY_UUID, "Custom");
cat.setDirectory(categoriesDir.resolve(CUSTOM_SCRIPTS_CATEGORY_UUID.toString()));
storeCategories.add(cat);
}
if (getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).isEmpty()) {
storeCategories.add(new DataStoreCategory(
categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()),
@ -218,7 +225,7 @@ public class StandardStorage extends DataStorage {
entry.setCategoryUuid(null);
}
storeEntries.add(entry);
storeEntries.put(entry, entry);
} catch (IOException ex) {
// IO exceptions are not expected
exception.set(ex);
@ -241,7 +248,7 @@ public class StandardStorage extends DataStorage {
ErrorEvent.fromThrowable(exception.get()).handle();
}
storeEntries.forEach(dataStoreCategory -> {
storeEntriesSet.forEach(dataStoreCategory -> {
if (dataStoreCategory.getCategoryUuid() == null
|| getStoreCategoryIfPresent(dataStoreCategory.getCategoryUuid())
.isEmpty()) {
@ -253,24 +260,24 @@ public class StandardStorage extends DataStorage {
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
}
var hasFixedLocal = storeEntries.stream().anyMatch(dataStoreEntry -> dataStoreEntry.getUuid().equals(LOCAL_ID));
var hasFixedLocal = storeEntriesSet.stream().anyMatch(dataStoreEntry -> dataStoreEntry.getUuid().equals(LOCAL_ID));
if (!hasFixedLocal) {
var e = DataStoreEntry.createNew(
LOCAL_ID, DataStorage.DEFAULT_CATEGORY_UUID, "Local Machine", new LocalStore());
e.setDirectory(getStoresDir().resolve(LOCAL_ID.toString()));
e.setConfiguration(
StorageElement.Configuration.builder().deletable(false).build());
storeEntries.add(e);
storeEntries.put(e, e);
e.validate();
}
var local = DataStorage.get().getStoreEntry(LOCAL_ID);
if (storeEntries.stream().noneMatch(entry -> entry.getColor() != null)) {
if (storeEntriesSet.stream().noneMatch(entry -> entry.getColor() != null)) {
local.setColor(DataStoreColor.BLUE);
}
refreshValidities(true);
storeEntries.forEach(entry -> {
storeEntriesSet.forEach(entry -> {
var syntheticParent = getSyntheticParent(entry);
syntheticParent.ifPresent(entry1 -> {
addStoreEntryIfNotPresent(entry1);
@ -280,8 +287,8 @@ public class StandardStorage extends DataStorage {
// Save to apply changes
if (!hasFixedLocal) {
storeEntries.removeIf(dataStoreEntry -> !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore);
storeEntries.stream().filter(entry -> entry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED).forEach(entry -> {
storeEntriesSet.removeIf(dataStoreEntry -> !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore);
storeEntriesSet.stream().filter(entry -> entry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED).forEach(entry -> {
entry.dirty = true;
entry.setStoreNode(DataStorageWriter.storeToNode(entry.getStore()));
});
@ -289,9 +296,15 @@ public class StandardStorage extends DataStorage {
}
deleteLeftovers();
loaded = true;
}
public synchronized void save() {
if (!loaded) {
return;
}
this.gitStorageHandler.prepareForSave();
try {
@ -322,7 +335,7 @@ public class StandardStorage extends DataStorage {
}
});
storeEntries.stream()
storeEntriesSet.stream()
.filter(dataStoreEntry -> dataStoreEntry.shouldSave())
.forEach(e -> {
try {

View file

@ -1,43 +0,0 @@
package io.xpipe.app.update;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.*;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.stage.Modality;
import java.nio.file.Files;
import java.util.function.UnaryOperator;
public class CommercializationAlert {
public static void showIfNeeded() {
if (AppState.get().isInitialLaunch()) {
AppCache.update("commercializationSeen", true);
return;
}
boolean set = AppCache.get("commercializationSeen", Boolean.class, () -> false);
if (set) {
return;
}
AppWindowHelper.showBlockingAlert(
alert -> {
alert.setTitle(AppI18n.get("news"));
alert.setAlertType(Alert.AlertType.NONE);
alert.initModality(Modality.NONE);
AppResources.with(AppResources.XPIPE_MODULE, "misc/commercialization.md", file -> {
var md = Files.readString(file);
var markdown = new MarkdownComp(md, UnaryOperator.identity()).createRegion();
alert.getDialogPane().setContent(markdown);
});
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
});
AppCache.update("commercializationSeen", true);
}
}

View file

@ -4,17 +4,13 @@ import io.xpipe.app.issue.ErrorEvent;
public class Hyperlinks {
public static final String WEBSITE = "https://xpipe.io";
public static final String DOCUMENTATION = "https://docs.xpipe.io";
public static final String GITHUB = "https://github.com/xpipe-io/xpipe";
public static final String ROADMAP = "https://xpipe.kampsite.co/";
public static final String PRIVACY = "https://github.com/xpipe-io/xpipe/blob/master/PRIVACY.md";
public static final String TOS = "https://github.com/xpipe-io/xpipe/blob/master/app/src/main/resources/io/xpipe/app/resources/misc/tos.md";
public static final String SECURITY = "https://github.com/xpipe-io/xpipe/blob/master/SECURITY.md";
public static final String PRIVACY = "https://docs.xpipe.io/privacy-policy";
public static final String EULA = "https://docs.xpipe.io/end-user-license-agreement";
public static final String SECURITY = "https://docs.xpipe.io/security";
public static final String DISCORD = "https://discord.gg/8y89vS8cRb";
public static final String SLACK =
"https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
public static final String DOCS_PRIVACY = "https://github.com/xpipe-io/xpipe/blob/master/PRIVACY.md";
public static final String SLACK = "https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
static final String[] browsers = {"xdg-open", "google-chrome", "firefox", "opera", "konqueror", "mozilla"};

View file

@ -1,10 +1,10 @@
package io.xpipe.app.util;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
@ -14,10 +14,6 @@ import org.kordamp.ikonli.javafx.FontIcon;
public class JfxHelper {
public static void fontSize(Node node, double relativeSize) {
node.setStyle(node.getStyle() + "-fx-font-size: " + (relativeSize) + "em;");
}
public static Region createNamedEntry(String nameString, String descString) {
var header = new Label(nameString);
AppFont.header(header);
@ -61,18 +57,18 @@ public class JfxHelper {
AppFont.header(header);
var desc = new Label(descString);
AppFont.small(desc);
var text = new VBox(header, desc);
text.setSpacing(2);
var text = new VBox(header, new Spacer(), desc);
if (image == null) {
return text;
}
var size = AppFont.getPixelSize(1) + AppFont.getPixelSize(-2) + 8;
var size = 40;
var graphic = PrettyImageHelper.ofFixedSquare(image, (int) size).createRegion();
var hbox = new HBox(graphic, text);
hbox.setAlignment(Pos.CENTER_LEFT);
hbox.setFillHeight(true);
hbox.setSpacing(10);
// graphic.fitWidthProperty().bind(Bindings.createDoubleBinding(() -> header.getHeight() +

View file

@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.util.JacksonMapper;
import org.apache.commons.io.FileUtils;
@ -17,17 +18,27 @@ import java.nio.file.Path;
public class JsonConfigHelper {
public static JsonNode readConfig(Path in) {
JsonNode node = JsonNodeFactory.instance.objectNode();
public static JsonNode readRaw(Path in) {
try {
if (Files.exists(in)) {
ObjectMapper o = JacksonMapper.getDefault();
node = o.readTree(Files.readAllBytes(in));
var read = o.readTree(Files.readAllBytes(in));
return read;
}
} catch (IOException e) {
ErrorEvent.fromThrowable(e).build().handle();
}
return node;
return JsonNodeFactory.instance.missingNode();
}
public static ObjectNode readConfigObject(Path in) {
var read = readRaw(in);
// Check the results of loading fails
if (read.isObject()) {
return (ObjectNode) read;
}
return JsonNodeFactory.instance.objectNode();
}
public static void writeConfig(Path out, JsonNode node) {

View file

@ -48,9 +48,13 @@ public class ScanAlert {
var providers = ScanProvider.getAll();
var applicable = new ArrayList<ScanProvider.ScanOperation>();
for (ScanProvider scanProvider : providers) {
ScanProvider.ScanOperation operation = scanProvider.create(entry, sc);
if (operation != null) {
applicable.add(operation);
try {
ScanProvider.ScanOperation operation = scanProvider.create(entry, sc);
if (operation != null) {
applicable.add(operation);
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
return applicable;
@ -62,125 +66,97 @@ public class ScanAlert {
}
private static void show(
DataStoreEntry initialStore, Function<DataStoreEntry, List<ScanProvider.ScanOperation>> applicable) {
DataStoreEntry initialStore, Function<DataStoreEntry, List<ScanProvider.ScanOperation>> applicable
) {
var entry = new SimpleObjectProperty<DataStoreEntryRef<ShellStore>>();
var selected = new SimpleListProperty<ScanProvider.ScanOperation>(FXCollections.observableArrayList());
var loading = new SimpleBooleanProperty();
Platform.runLater(() -> {
var stage = AppWindowHelper.sideWindow(
AppI18n.get("scanAlertTitle"),
window -> {
return new MultiStepComp() {
var stage = AppWindowHelper.sideWindow(AppI18n.get("scanAlertTitle"), window -> {
return new MultiStepComp() {
private final StackPane stackPane = new StackPane();
private final StackPane stackPane = new StackPane();
{
stackPane.getStyleClass().add("scan-list");
}
{
stackPane.getStyleClass().add("scan-list");
}
@Override
protected List<Entry> setup() {
return List.of(new Entry(AppI18n.observable("a"), new Step<>() {
@Override
protected List<Entry> setup() {
return List.of(new Entry(AppI18n.observable("a"), new Step<>() {
@Override
public CompStructure<?> createBase() {
var b = new OptionsBuilder()
.name("scanAlertChoiceHeader")
.description("scanAlertChoiceHeaderDescription")
.addComp(new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.OTHER,
null,
entry,
ShellStore.class,
store1 -> true,
StoreViewState.get().getAllConnectionsCategory()
)
.disable(new SimpleBooleanProperty(initialStore != null)))
.name("scanAlertHeader")
.description("scanAlertHeaderDescription")
.addComp(Comp.of(() -> stackPane).vgrow())
.buildComp()
.prefWidth(500)
.prefHeight(600)
.styleClass("window-content")
.apply(struc -> {
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
})
.createStructure()
.get();
public CompStructure<?> createBase() {
var b = new OptionsBuilder().name("scanAlertChoiceHeader").description("scanAlertChoiceHeaderDescription").addComp(
new DataStoreChoiceComp<>(DataStoreChoiceComp.Mode.OTHER, null, entry, ShellStore.class, store1 -> true,
StoreViewState.get().getAllConnectionsCategory()).disable(
new SimpleBooleanProperty(initialStore != null))).name("scanAlertHeader").description(
"scanAlertHeaderDescription").addComp(Comp.of(() -> stackPane).vgrow()).buildComp().prefWidth(500).prefHeight(
600).styleClass("window-content").apply(struc -> {
VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS);
}).createStructure().get();
entry.addListener((observable, oldValue, newValue) -> {
selected.clear();
stackPane.getChildren().clear();
entry.addListener((observable, oldValue, newValue) -> {
selected.clear();
stackPane.getChildren().clear();
if (newValue == null) {
return;
}
ThreadHelper.runAsync(() -> {
BooleanScope.execute(loading, () -> {
var a = applicable.apply(entry.get().get());
Platform.runLater(() -> {
if (a == null) {
window.close();
return;
}
selected.setAll(a.stream()
.filter(
scanOperation ->
scanOperation.isDefaultSelected())
.toList());
var r = new ListSelectorComp<>(
a,
scanOperation ->
AppI18n.get(scanOperation.getNameKey()),
selected,
a.size() > 3
)
.createRegion();
stackPane.getChildren().add(r);
});
});
});
});
entry.set(initialStore != null ? initialStore.ref() : null);
return new SimpleCompStructure<>(b);
}
}));
}
@Override
protected void finish() {
ThreadHelper.runAsync(() -> {
if (entry.get() == null) {
if (newValue == null) {
return;
}
ThreadHelper.runAsync(() -> {
BooleanScope.execute(loading, () -> {
var a = applicable.apply(entry.get().get());
Platform.runLater(() -> {
window.close();
});
Platform.runLater(() -> {
if (a == null) {
window.close();
return;
}
BooleanScope.execute(loading, () -> {
entry.get().get().setExpanded(true);
for (var a : selected) {
try {
a.getScanner().run();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
selected.setAll(a.stream().filter(scanOperation -> scanOperation.isDefaultSelected()).toList());
var r = new ListSelectorComp<>(a, scanOperation -> AppI18n.get(scanOperation.getNameKey()), selected,
a.size() > 3).createRegion();
stackPane.getChildren().add(r);
});
});
});
});
entry.set(initialStore != null ? initialStore.ref() : null);
return new SimpleCompStructure<>(b);
}
};
},
false,
loading);
}));
}
@Override
protected void finish() {
ThreadHelper.runAsync(() -> {
if (entry.get() == null) {
return;
}
Platform.runLater(() -> {
window.close();
});
BooleanScope.execute(loading, () -> {
entry.get().get().setExpanded(true);
var copy = new ArrayList<>(selected);
for (var a : copy) {
try {
a.getScanner().run();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
});
});
}
};
}, false, loading);
stage.show();
});
}

View file

@ -34,7 +34,7 @@ public class SecretRetrievalStrategyHelper {
var content = new HorizontalComp(List.of(
new TextFieldComp(keyProperty).apply(struc -> struc.get().setPromptText("Password key")).hgrow(),
new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
AppPrefs.get().selectCategory(4);
AppPrefs.get().selectCategory(5);
App.getApp().getStage().requestFocus();
})
.grow(false, true)))
@ -63,38 +63,41 @@ public class SecretRetrievalStrategyHelper {
p);
}
public static OptionsBuilder comp(Property<SecretRetrievalStrategy> s) {
public static OptionsBuilder comp(Property<SecretRetrievalStrategy> s, boolean allowNone) {
SecretRetrievalStrategy strat = s.getValue();
var inPlace = new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.InPlace i ? i : null);
var passwordManager =
new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.PasswordManager i ? i : null);
var customCommand =
new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.CustomCommand i ? i : null);
var command = new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.CustomCommand c ? c : null);
var map = new LinkedHashMap<String, OptionsBuilder>();
map.put("none", new OptionsBuilder());
if (allowNone) {
map.put("none", new OptionsBuilder());
}
map.put("password", inPlace(inPlace));
map.put("passwordManager", passwordManager(passwordManager));
map.put("customCommand", customCommand(customCommand));
map.put("prompt", new OptionsBuilder());
int offset = allowNone ? 0 : -1;
var selected = new SimpleIntegerProperty(
strat instanceof SecretRetrievalStrategy.None
? 0
? offset
: strat instanceof SecretRetrievalStrategy.InPlace
? 1
? offset + 1
: strat instanceof SecretRetrievalStrategy.PasswordManager
? 2
? offset + 2
: strat instanceof SecretRetrievalStrategy.CustomCommand
? 3
? offset + 3
: strat instanceof SecretRetrievalStrategy.Prompt
? 4
? offset + 4
: strat == null ? -1 : 0);
return new OptionsBuilder()
.choice(selected, map)
.bindChoice(
() -> {
return switch (selected.get()) {
case 0 -> new SimpleObjectProperty<>(new SecretRetrievalStrategy.None());
return switch (selected.get() - offset) {
case 0 -> new SimpleObjectProperty<>(allowNone ? new SecretRetrievalStrategy.None() : null);
case 1 -> inPlace;
case 2 -> passwordManager;
case 3 -> customCommand;

View file

@ -3,6 +3,8 @@ package io.xpipe.app.util;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.WinReg;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl;
import java.util.Optional;
@ -30,4 +32,30 @@ public class WindowsRegistry {
return Optional.empty();
}
}
public static Optional<String> readRemoteString(ShellControl shellControl, int hkey, String key, String valueName) throws Exception {
var command = CommandBuilder.of().add("reg", "query").addQuoted((hkey == HKEY_LOCAL_MACHINE ? "HKEY_LOCAL_MACHINE" : "HKEY_CURRENT_USER") + "\\" + key).add("/v").addQuoted(valueName);
String output;
try (var c = shellControl.command(command).start()) {
output = c.readStdoutDiscardErr();
if (c.getExitCode() != 0) {
return Optional.empty();
}
}
// Output has the following format:
// \n<Version information>\n\n<key>\t<registry type>\t<value>
if (output.contains("\t")) {
String[] parsed = output.split("\t");
return Optional.of(parsed[parsed.length - 1]);
}
if (output.contains(" ")) {
String[] parsed = output.split(" ");
return Optional.of(parsed[parsed.length - 1]);
}
return Optional.empty();
}
}

View file

@ -53,7 +53,7 @@ addAutomatically=Search Automatically ...
addOther=Add Other ...
addStreamTitle=Add Stream Store
addConnection=Add Connection
addConnections=Add Connections ...
addConnections=New
selectType=Select Type
selectTypeDescription=Select connection type
selectDatabaseType=Database Type

View file

@ -18,8 +18,7 @@ selectAll=Select all
command=Command
advanced=Advanced
thirdParty=Open source notices
termsOfService=Terms of service
termsOfServiceDescription=Read the terms of service for the XPipe application
eulaDescription=Read the End User License Agreement for the XPipe application
thirdPartyDescription=View the open source licenses of third-party libraries
workspaceLock=Master passphrase
enableGitStorage=Enable git storage
@ -52,7 +51,7 @@ exit=Quit XPipe
continueInBackground=Continue in background
minimizeToTray=Minimize to tray
closeBehaviourAlertTitle=Set closing behaviour
closeBehaviourAlertTitleHeader=Select what should happen when closing the window. Any active tunnels will be closed when the application is shut down.
closeBehaviourAlertTitleHeader=Select what should happen when closing the window. Any active connections will be closed when the application is shut down.
startupBehaviour=Startup behaviour
startupBehaviourDescription=Controls the default behavior of the desktop application when XPipe is started.
clearCachesAlertTitle=Clean Cache

View file

@ -228,7 +228,7 @@ updateAvailableTooltip=Update available
visitGithubRepository=Visit GitHub repository
updateAvailable=Update available: $VERSION$
downloadUpdate=Download update
legalAccept=I accept the terms of service
legalAccept=I accept the End User License Agreement
confirm=Confirm
print=Print
whatsNew=What's new in version $VERSION$?
@ -236,8 +236,7 @@ antivirusNoticeTitle=A note on Antivirus programs
updateChangelogAlertTitle=Changelog
greetingsAlertTitle=Welcome to XPipe
gotIt=Got It
eula=End-user license agreement
tos=Terms of service
eula=End User License Agreement
news=News
introduction=Introduction
privacyPolicy=Privacy Policy

View file

@ -1,21 +0,0 @@
# Announcement
XPipe has been out now for a few months and during that time I received a lot of feedback and a small community formed.
This helped me to get a lot of insight into the users, use cases, and requirements, and I was able to constantly improve XPipe.
## Going full-time
The demand for XPipe so far also convinced that there is a market to develop XPipe full-time and finance it by providing commercial and enterprise plans for interested customers. So I decided to go for it! This will improve the development speed and quality as I can now fully focus creating the best possible application.
I think a lot of software users have been scarred by such announcements of projects going commercial as that usually results in features being paywalled and changing licenses. I can assure you though that this is not the case with XPipe. The scope is fairly small and only involves me, so no investors or other employees. This drastically lowers the break-even value compared to other products and allows me to implement a more lenient commercialization.
## What does this mean for me?
Essentially, you can use all current features without any limitation for free, unless you use it for commercial purposes, which now requires a professional license upgrade. Furthermore, most upcoming features will also be included in the free version. The only exception are features purposefully made for team collaboration and tools only used in enterprises. For now, the only professional-exclusive feature is the newly added git repository storage, which allows you to share your XPipe configurations with others via your own git remote repositories. The open source model and license also won't change. Some professional-exclusive features will probably not be included in the repository though.
So if you intend to use XPipe in commercial contexts, I would like you to ask to buy a professional license for that. You can of course also do that if you want to support the project without any commercial intentions.
## Outlook
I hope that everything works out and that I can make this endeavor work. That way I can continuously keep improving XPipe!
There are a lot of cool new features already in the pipeline that will be released relatively soon.

View file

@ -0,0 +1,65 @@
# End User License Agreement
## Preamble
This end user license agreement (hereinafter “**EULA**”) is a license agreement between you as the end user and **XPipe UG (haftungsbeschränkt)**, Reichertshalde 81, 71642 Ludwigsburg, (hereinafter “**XPipe**”) for the use of the products “XPipe”, a server connection and server management software, which was developed by XPipe (hereinafter “**Software**”).
This document can be viewed and printed out at any time in the XPipe desktop application or at http://docs.xpipe.io/end-user-license-agreement. The customer is independently responsible for saving the text of the contract at the time the contract is concluded. No copies will be provided by XPipe after the fact.
## § 1 Subject matter and validity of the EULA
(1) The EULA governs the terms under which you may use the Software. By accepting the EULA or using the Software, you agree to be bound by the terms of the EULA.
(2) This EULA does not establish a service relationship between XPipe and you in return for payment. The payment of a license fee is solely the subject of the contractual relationship between you and the payment service provider Lemonsqueezy, to whom you pay license fees and in return we grant you a right to use additional software features of the professional plan. “Lemonsqueezy” collectively means Lemon Squeezy, LLC, 222 South Main Street Suite 500, Salt Lake City, Utah. Lemon Squeezy, LLC is a global payment service provider that facilitates online transactions using common online payment methods and acts as the seller of the product.
## § 2 License
(1) By entering into the EULA, you receive the non-exclusive, non-transferable and non-sublicensable right, unlimited in time, to run the Software installed on a computer in machine-readable format in accordance with the terms of the EULA (“License”).
(2) The rights granted with the license include the right to obtain updates of the software provided by XPipe for the period of an active subscription. The use of updates is voluntary. Insofar as new software functions are introduced with an update, this does not justify any claim by you that these will also be maintained with future updates.
## § 3 Provision of the Software, Activation of the Subscription
(1) The software is made available by you downloading it from the XPipe website and installing it independently on your device. XPipe itself owes you neither provision nor installation or integration of the software on a computer.
(2) Any active subscription is checked for validity each time the program is started. For this purpose, an Internet connection is required at least once a week when the program is started. Any circumvention of a subscription activation and any unauthorized use of the software not authorized by XPipe and in violation of this EULA is prohibited.
(3) Activation of a subscription is done individually for you as an end user, and the activated subscription is verified when the program is started. You may install the software on multiple computers.
(4) Any circumvention of the aforementioned activation is prohibited.
## § 4 Rights to the Software
(1) Subject to the terms of any license granted and except as expressly provided in this EULA or otherwise agreed, you acquire no rights in the Software or Documentation. All Software and Documentation provided by XPipe, all copies, compilations, derivative products, programmatic enhancements, patches, revisions and updates to or relating to the Software, and all patent, utility model, trademark, design and copyright, trade secret, trade name and any other invention, design and information protected by law contained in any of the foregoing are and shall remain the property or ownership of XPipe.
(2) You are prohibited from asserting the rights set forth in § 5 (1) or claims for the granting of any of the aforementioned rights to the Software or the Documentation and, in particular, from registering or claiming intellectual property rights to the Software against XPipe or third parties.
(3) With the exception of §§ 69d, 69e UrhG, the customer is prohibited from attempting to reconstruct, decompile or decrypt any source code or underlying ideas or algorithms of any software or to permit a third party to do so. Except as otherwise provided in this EULA, or as otherwise expressly agreed between the parties, you may not modify the Software, make it available for use by third parties against payment, sublet it, or otherwise transfer any rights of use to third parties. Your right to execute the software installed on a specific computer in accordance with this EULA remains unaffected.
(4) You are obligated to notify XPipe immediately of any violations of these provisions or any other violations of XPipe's rights to the software that are known to you or become known to you in the future.
## § 5 No warranty
XPipe does not warrant to you that the software has a certain quality or a certain scope of functions and is free of defects.
## § 6 Liability
(1) XPipe shall be liable without limitation in the event of (i) intent and gross negligence, (ii) for injury to life, limb and health, (iii) in accordance with the provisions of the Product Liability Act, from Art. 82 of Regulation 2016/670/EU (DSGVO) or other mandatory statutory liability provisions in accordance with the standards set forth therein, and (iv) to the extent of any warranty assumed.
(2) XPipe shall further be liable for culpable breach of a material contractual obligation, the fulfillment of which is a prerequisite for proper performance of the contract and compliance with which the contractual partner may regularly rely on (“cardinal obligation”), but in the event of simple (minor) negligence limited to the damage reasonably to be expected and foreseeable at the time of conclusion of the contract.
(3) XPipe shall have no further liability.
(4) The above limitation of liability shall also apply to the personal liability of employees, representatives and members of XPipe's governing bodies.
## § 7 Data protection
XPipe is obligated to comply with the applicable data protection regulations. Information on data protection and the processing of personal data by XPipe can be found in the privacy policy for the Software: https://docs.xpipe.io/privacy-policy
## § 8 Final provisions
(2) Neither party may transfer or assign this EULA in whole or any rights or obligations hereunder without the prior written consent of the other party. Any such transfer or assignment shall be void. Notwithstanding the foregoing, (i) a party may assign this agreement to a third party that assumes all or substantially all of its related business activities as a result of a merger, sale of shares or assets, or similar transaction, and (ii) XPipe may use third parties to perform its obligations under this agreement, provided that XPipe shall remain liable for any breach of such obligations.
(3) This EULA shall be governed by the laws of the Federal Republic of Germany, excluding the United Nations Convention on Contracts for the International Sale of Goods (CISG) and conflict of law provisions. The place of performance and jurisdiction shall be Stuttgart, Germany.
(4) If any provision of this EULA is invalid, void or unenforceable under any present or future law, the remainder of this EULA shall continue in full force and effect. To the extent that the invalid, void or unenforceable provision is a material term of the agreement, the parties agree to jointly negotiate a valid alternative provision.

View file

@ -1,149 +0,0 @@
## Terms of Service
XPipe UG (haftungsbeschränkt), located at Reichertshalde 81, 71642 Ludwigsburg, Germany ("XPipe") specializes in server connection and server management solutions and offers the XPipe desktop application software.
The customer intends to use XPipe's server connection management software for private or business purposes. To enable the customer to use the software, the customer is granted a non-exclusive, non-transferable, time-limited right to use the software in accordance with these terms of service ("Terms of Service").
The Terms of Service can be viewed and printed out at any time in the XPipe desktop application or at http://docs.xpipe.io/terms-of-service. The customer is independently responsible for saving the text of the contract at the time the contract is concluded. No copies will be provided by XPipe after the fact.
## 1. Subject of the agreement
**1.1** Subject to these Terms of Service and the service description, XPipe grants customer access to the products in accordance with the service description and applicable documentation for the term of this agreement.
**1.2** The customer shall pay XPipe the agreed remuneration as specified in the online purchase process or in the order form, insofar as the customer receives services that are marked as subject to a charge upon conclusion of the contract.
## 2. Scope
**2.1** The XPipe desktop application for server and server connection management ("Software") is an offer of XPipe UG (haftungsbeschränkt) (hereinafter: "XPipe" or "we").
**2.2** These Terms of Service shall apply to the contractual relationship between XPipe and the User (hereinafter: "User" or "you") regarding the use of this offer. Deviating Terms of Service or deviating conditions of the User shall not become part of the contract, even if XPipe does not expressly object to them.
## 3. Conclusion of contract and contractual relationships; Registration requirements; Modifications of the Terms of Service
**3.1** By clicking on the button "start trial" or "submit order" on the shop page at https://buy.xpipe.io you submit an offer to us to order the paid subscriptions and/or free trial offers selected by you in a legally binding manner and subject to payment. A contract for the services ordered by you comes into effect with our order confirmation, which we send to the e-mail address specified by you in the order immediately after receiving your order. Please check your SPAM folder regularly after placing your order with us in order to avoid that our order confirmation is directed there by your IT systems and remains unnoticed. With the order confirmation, you will also receive the license keys required to activate the subscriptions you ordered.
**3.2** XPipe shall not store the terms and conditions of the contract for the User, without prejudice to the fulfillment of the statutory information obligations.
**3.3** The User must have full legal capacity or act with the consent of his legal representative. In any case, the User must be at least 18 years old.
**3.4** XPipe reserves the right to amend these Terms of Service even after conclusion of the contract with effect for existing contractual relationships, provided that this appears necessary to safeguard the legitimate interests of XPipe, is not unreasonable for the User and the User is not disadvantaged thereby contrary to good faith. XPipe shall consider changes to be reasonable that (i) are exclusively beneficial to the User, (ii) are necessary due to purely technical changes, (iii) are necessary due to a change in the applicable or newly introduced legal situation, (iv) must be made due to a court decision or official order against us, or (v) insofar as we introduce additional services, services or service components that require a service description in the Terms of Service. Explicitly excluded from the foregoing right to make changes are changes that result in a change in the character of the main contractual service owed by us that is disadvantageous to the User. To this end, XPipe shall inform users of the changes in text form at least four weeks before the new terms and conditions come into effect. Users shall have the opportunity to object to the changes. In this case, both parties shall have an extraordinary right of termination. If no objection is made, the changes shall be deemed approved four weeks after receipt of the notification. XPipe shall inform the User of the right to object and the effect of silence in the notification. The User's right of termination pursuant to clause 7 shall remain unaffected.
## 4. Service description and scope
**4.1** Insofar as a payment or paid subscription agreement has been concluded, XPipe shall provide the agreed services. Insofar as XPipe also provides content and/or services voluntarily and free of charge, it shall do so without assuming any legal obligation. XPipe shall be entitled to change, expand or limit these services at any time.
**4.2** XPipe guarantees an availability of 99% or higher, based on the calendar year, for the server for downloading the Software (this currently corresponds to the XPipe website) as well as for the server for registration and/or obtaining licenses. Not included are downtimes caused by necessary maintenance work, force majeure, technical disruptions of the Internet or other reasons for which XPipe is not responsible.
**4.3** XPipe assumes no obligation to back up data for the User and makes no representations, warranties or guarantees beyond those required by law, unless otherwise agreed in individual cases.
## 5. Duties and responsibilities of the Users; Improper conduct
**5.1** The User may use XPipe's offer exclusively for the agreed purpose, whether private or business. In particular, the customer may not use the data, information or services provided to him/her for the following purposes without the express written consent of XPipe and shall not permit third parties to do so:
- (a) for all illegal activities, especially circumvention of export control laws and regulations.
- (b) to the production of programs, writings and the like, if thereby rights of third parties are injured;
- (c) for decompiling and reverse engineering the program code.
**5.2** The User undertakes to provide truthful and complete information upon registration and to keep this information up to date during the term of the agreement by adjusting it or notifying XPipe.
**5.3** Only one registration may be maintained per person at any one time.
**5.4** The User is obliged to keep his access data secret and not to pass it on to third parties. The transfer of a paid subscription to third parties is prohibited.
**5.5** The User is obliged to observe the statutory copyrights and other rights existing for the contents of the paid subscriptions as well as for the other contents in the XPipe offer. The User may not reproduce, distribute or make publicly available such content or remove technical protective measures or copyright or legal notices unless this is expressly permitted.
**5.6** The User shall be obliged to provide, at his own expense, hardware and software as well as an Internet connection for retrieving the agreed content and/or services from the XPipe offer.
## 6. Terms of payment, payment service providers, set-off and right of retention
**6.1** For secure payment processing, the check-out process on our website is facilitated by Lemonsqueezy, a global payment processor and merchant of record. Lemonsqueezy is a secure and reputable platform that provides payment services to online businesses. By purchasing our software, you agree to Lemonsqueezys terms and conditions, https://www.lemonsqueezy.com/buyer-terms. We do not assume any liability or responsibility for Lemonsqueezys services or actions. Read more at https://www.lemonsqueezy.com.
**6.2** The User agrees to pay within the set time for the chosen payment method, unless otherwise agreed. Fees of a subscription plan must be paid at the beginning of the period, unless otherwise agreed.
**6.3** We may occasionally offer gift cards and vouchers that can be used as a payment method.
**6.4** The User shall only have a right of set-off if his claim against XPipe has been legally established, is undisputed or has been acknowledged. The User shall only have a right of retention if its counterclaim is based on the same contractual relationship.
**6.5** If the User unjustifiably fails to meet his or her due payment obligations to XPipe, XPipe may, after prior warning and taking into account other legal and contractual rights, temporarily block the User's access until the payment owed has been received. The temporary blocking shall not affect the term of the contract. User may incur late payment charges and interest.
**6.6** The User shall, in accordance with law, reimburse XPipe for any damages and necessary expenses incurred as a result of non-payment or late payment by the User or other disruption of payment by the User, unless the User is not responsible therefor.
## 7. Contract duration, automatic renewal, pause, termination
**7.1** The paid subscription contract is valid for a certain minimum term. For example, it can be one (1) month. At the end of each subscription period, the subscription will be automatically renewed depending on the term of the subscription period unless terminated by User before the last day of the current subscription period.
**7.2** The User or XPipe may terminate the subscription contract at any time, with one day's notice before the end of the minimum term. The User may continue to use the XPipe service until the end of the minimum term. If the contract is not properly terminated by the end of the minimum term, it will be automatically renewed and a paid subscription will be activated.
**7.3** For the effective termination, the user may give notice of termination online via the function provided on the billing page at https://buy.xpipe.io/billing. Users can always contact us at hello@xpipe.io for help with ending their subscription.
**7.4** The right of both contracting parties to terminate for cause in accordance with the law shall remain unaffected. XPipe shall have an extraordinary reason for termination if the User provides misleading information during registration or ordering or has repeatedly violated the Terms of Service. The prerequisite for this is that XPipe has previously warned the User unsuccessfully in order to demand compliance with the contractual obligations.
**7.5** If XPipe offers a free trial period for a paid subscription and this is agreed with the User, the agreed contract term of the paid subscription shall be extended accordingly. In this case, the extended period shall apply first (with an ordinary special termination right of the User, if applicable, if agreed) and thereafter the regular term of the paid subscription.
**7.6** Within a free trial period, User and XPipe are allowed to terminate the contract at any time with immediate effect.
**7.7** In the event of an act with fraudulent intent, a severe, persisting, imminent or repeated material breach of these Terms of Service, XPipe is entitled to suspend the User's access to the Software immediately and indefinitely. User is informed in writing (email is sufficient). The suspicion of fraudulent intent is sufficient.
## 8. Liability
**8.1** XPipe shall in principle only be liable for damages to the User if (1) XPipe, its legal representatives or vicarious agents have caused such damages intentionally or through gross negligence or (2) this is based on a breach of duty by XPipe or one of its legal representatives or vicarious agents that results in injury to the life, body or health of the User. In cases of liability under the German Product Liability Act (Produkthaftungsgesetz, ProdHaftG), the assumption of a guarantee or due to fraudulent misrepresentation, as well as the breach of an obligation the fulfillment of which makes the proper execution of the contract possible in the first place and on the fulfillment of which the User may regularly rely on (so-called cardinal obligation), XPipe shall be liable without limitation in cases (1) and (2) of the preceding sentence. Otherwise, liability shall be limited to the foreseeable damage typical for the contract.
**8.2** In all other cases not mentioned in (1) and (2) above and except as provided in the following paragraph, XPipe's liability shall be excluded regardless of the legal ground.
**8.3** The above limitations of liability apply accordingly to all employees and vicarious agents of XPipe and do not affect the statutory burden of proof. Please note that the statutory provisions shall remain valid.
## 9. Right of revocation
**9.1** If the customer is a consumer within the meaning of § 13 German Civil Code (Bürgerliches Gesetzbuch, BGB), the following right of revocation shall apply to services subject to a charge:
**Right of revocation** You have the right to revoke this contract within fourteen days without giving any reason. The revocation period is fourteen days from the day of the conclusion of the contract. To exercise your right of revocation, you must inform us (XPipe UG (haftungsbeschränkt), Reichertshalde 81, 71642 Ludwigsburg, Germany, hello@xpipe.io) of your decision to revoke this contract by means of a clear declaration (e.g. a letter sent by post or an email). For this purpose, you may use the enclosed sample revocation form, which, however, is not mandatory. To comply with the revocation period, it is sufficient that you send the notification of the exercise of the right of revocation before the expiry of the revocation period.
**Consequences of revocation** If you revoke this contract, we shall reimburse you all payments we have received from you, including delivery costs without undue delay and no later than within fourteen days from the day on which we received the notification of your revocation of this contract. For this repayment, we will use the same means of payment that you used for the original transaction, unless expressly agreed otherwise with you; in no case will you be charged any fees because of this repayment. If you have requested that the services begin during the revocation period, you shall pay us a reasonable amount corresponding to the proportion of the services already provided up to the time you notify us of the exercise of the right of revocation with respect to this contract compared to the total scope of the services provided for in the contract.
Sample cancellation form
To XPipe UG (haftungsbeschränkt), Reichertshalde 81, 71642 Ludwigsburg, Germany, hello@xpipe.io:
I/we (*) hereby revoke the contract concluded by me/us (*) for the purchase of the following goods (*)/provision of the following service (*),
Ordered on (*)/received on (*),
Name of the consumer(s),
Address of the consumer(s),
Signature of consumer(s) (only in case of paper communication),
Date.
(*) Please delete where inapplicable
**9.2** If the customer is a consumer within the scope of § 13 German Civil Code (Bürgerliches Gesetzbuch, BGB), the customer shall be entitled to the statutory warranty rights.
**9.3** If the customer is an entrepreneur within the meaning of § 14 of the German Civil Code (BGB), XPipe shall assume warranty for the products in the case of paid subscriptions only to the extent set forth in the following provisions.
**9.4** If the products XPipe provides are defective, XPipe will provide an improved or new product within a reasonable time after receiving a written complaint from the customer. If the Software is used by a third party licensed by XPipe, publicly available upgrades, updates or patches shall be deemed sufficient.
**9.5** The customer may reduce the agreed remuneration by a reasonable amount if proper performance of the services owed is not guaranteed within a reasonable period set by the customer for reasons for which XPipe is responsible. The right of reduction shall be limited to that part of the services that is defective in relation to the monthly remuneration.
**9.6** If the reduction pursuant to Section **9.5** continues for two (2) consecutive months or for two (2) months of a quarter, the customer may terminate the Agreement without notice.
**9.7** The customer shall inform XPipe immediately in text form (e.g. to hello@xpipe.io) of any defects that occur.
**9.8** The customer is obligated to support XPipe free of charge in the elimination of defects, in particular by providing all necessary documents, data and other information required to analyze and eliminate the defects.
**9.9** In the event of the provision of additional services free of charge, XPipe shall only be liable for defects if XPipe has fraudulently concealed the defect.
## 10. Final provisions
**10.1** There are no oral or written collateral agreements.
**10.2** Should individual provisions of these Terms of Service or the concluded contract be or become invalid in whole or in part, the remainder of the contract shall remain valid. The invalid provision shall be replaced by the statutory provision.
**10.3** German law shall apply to the exclusion of the UN Convention on Contracts for the International Sale of Goods.
**10.4** The agreed place of jurisdiction for all disputes arising from the contractual relationship between the User and XPipe shall be XPipe's registered office, provided the User is a merchant, a legal entity under public law or a special fund under public law. Notwithstanding the foregoing, XPipe shall also be entitled to sue the User at the user's statutory place of jurisdiction.
**10.5** In the event of complaints about XPipe, the User may at any time contact the European Platform for Online Dispute Resolution in Consumer Matters: https://ec.europa.eu/consumers/odr.
**10.6** XPipe is not obligated or willing to participate in dispute resolution proceedings before a consumer arbitration board.
**10.7** If any provision of this Terms of Service is invalid, void or unenforceable under any present or future law, the remainder of this Terms of Service shall continue in full force and effect. To the extent that the invalid, void or unenforceable provision is a material term of the agreement, the parties agree to jointly negotiate a valid alternative provision.
As of: 20th September 2023

View file

@ -1,7 +1,21 @@
.download-background {
-fx-border-color: -color-border-default;
-fx-border-width: 1px 0 0 0;
-fx-padding: 1em;
}
.transfer .button {
-fx-border-color: -color-border-default;
-fx-border-width: 1px 0 0 0;
-fx-padding: 1em;
-fx-border-width: 1px;
-fx-background-color: -color-bg-default;
-fx-background-radius: 4;
-fx-border-radius: 4;
-fx-padding: 0.1em 0.2em;
}
.transfer .button:hover {
-fx-background-color: -color-bg-subtle;
-fx-opacity: 1.0;
}
.browser .welcome .button {

View file

@ -1,13 +1,17 @@
.category-button {
-fx-opacity: 0.8;
-fx-border-width: 0;
-fx-background-color: transparent;
-fx-padding: 0 0 0 2;
-fx-background-insets: 0;
}
.category-button .settings {
-fx-opacity: 1.0;
}
.category-button:hover .name * {
-fx-underline: true ;
.category-button:hover, .category-button:focused {
-fx-background-color: -color-bg-default;
}
.category:selected .category-button {
@ -20,11 +24,13 @@
}
.category .separator{
-fx-padding: 0;
-fx-padding: 0 5 0 5;
}
.category .separator .line {
-fx-pref-height: 1;
-fx-background-color: -color-fg-default;
-fx-opacity: 0.5;
}

View file

@ -8,6 +8,16 @@
-fx-border-color: derive(-color-border-default, -10%);
}
.root:dark .color-box.gray {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, derive(-color-bg-default, 13%) 40%, derive(-color-bg-default, 11%) 50%, derive(-color-bg-default, 14%) 100%);
-fx-border-color: -color-border-default;
}
.root:light .color-box.gray {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, derive(-color-bg-default, -5%) 40%, derive(-color-bg-default, -3%) 50%, derive(-color-bg-default, -5%) 100%);
-fx-border-color: derive(-color-border-default, -10%);
}
.color-box > .separator .line {
-fx-border-color: -color-border-default;
}

View file

@ -29,15 +29,19 @@
}
.store-header-bar .menu-button {
-fx-background-radius: 4px;
-fx-border-radius: 4px;
-fx-background-radius: 3px;
-fx-border-radius: 3px;
-fx-border-width: 1px;
-fx-padding: 0;
-fx-background-color: -color-fg-default;
-fx-padding: -3 0 -3 0;
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(208, 220, 252) 40%, rgba(219, 219, 255, 1) 50%, rgb(250, 242, 242) 100%);
-fx-border-color: transparent;
}
.store-header-bar .menu-button:hover {
.root:light .store-header-bar .menu-button {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(12, 11, 11) 40%, rgb(32, 32, 40) 50%, rgb(35, 29, 29) 100%);
}
.root:light .store-header-bar .menu-button:hover, .root:dark .store-header-bar .menu-button:hover {
-fx-background-color: -color-bg-default;
-fx-border-color: -color-fg-default;
}
@ -147,24 +151,27 @@
}
.bar .name {
-fx-font-weight: BOLD;
-fx-text-fill: -color-fg-default;
}
.bar .count-comp {
-fx-font-weight: BOLD;
-fx-padding: 0.0em 0em 0.0em 0.4em;
-fx-text-fill: -color-fg-muted;
}
.bar .filter-bar .text-field {
-fx-padding: 0.35em;
-fx-text-fill: -color-fg-default;
}
.bar .filter-bar {
-fx-background-radius: 4px;
-fx-background-color: -color-bg-default;
-fx-padding: -3 0 -3 0;
-fx-background-radius: 3px;
-fx-background-color: -color-bg-default;
-fx-border-color: -color-fg-default;
-fx-border-width: 0.05em;
-fx-border-radius: 4px;
-fx-border-color: -color-border-default;
-fx-border-radius: 3px;
}

View file

@ -0,0 +1,10 @@
.side-split-pane-comp.split-pane-divider {
-fx-padding: 1;
}
.side-split-pane-comp .split-pane-divider .horizontal-grabber {
-fx-padding: 0;
-fx-background-color: transparent;
-fx-background-insets: 0;
-fx-shape: " ";
}

View file

@ -20,7 +20,7 @@
-fx-opacity: 1.0;
}
.sidebar-comp .icon-button-comp:hover {
.sidebar-comp .icon-button-comp:hover, .sidebar-comp .icon-button-comp:focused {
-fx-border-color: -color-accent-muted;
-fx-background-color: -color-neutral-muted;
}

View file

@ -1,66 +0,0 @@
.storage-entry-comp .jfx-text-field {
-fx-padding: 0;
}
.storage-entry-comp .content {
-fx-padding: 0.65em 0 0.65em 0;
-fx-background-color: DDD;
-fx-background-radius: 2px 0 0 2px;
-fx-border-width: 1px;
-fx-border-color: -xp-border-accent;
-fx-border-radius: 2px 0 0 2px;
}
.storage-entry-comp .update {
-fx-border-width: 1px 1px 1px 0;
-fx-border-color: -xp-border-accent;
-fx-background-color: -xp-base-accent;
-fx-background-radius: 0;
-fx-border-radius: 0;
-fx-text-fill: -xp-text-accent-base;
-fx-font-size: 1.15em;
}
.storage-entry-comp .retrieve {
-fx-border-width: 1px 1px 1px 0;
-fx-border-color: -xp-border-accent;
-fx-background-color: -xp-base-accent;
-fx-background-radius: 0;
-fx-border-radius: 0;
-fx-text-fill: -xp-text-accent-base;
-fx-font-size: 1.15em;
}
.storage-entry-comp .settings {
-fx-border-width: 1px 1px 1px 0;
-fx-border-color: -xp-border-accent;
-fx-text-fill: -xp-text-base;
-fx-font-size: 1.15em;
-fx-background-color: DDD;
-fx-background-radius: 0 2px 2px 0;
-fx-border-radius: 0 2px 2px 0;
}
.storage-entry-comp .text-field {
-fx-padding: 0;
}
.storage-entry-comp .input-line {
-fx-opacity: 0.2;
}
.storage-entry-comp .data-store-type-comp .ikonli-font-icon {
-fx-icon-color: #222;
}
.storage-entry-comp .readable-instant-comp, .storage-entry-comp .size {
-fx-text-fill: -xp-text-light;
}
.storage-entry-comp .information, .storage-entry-comp .summary, .storage-entry-comp .date {
-fx-text-fill: -xp-text-light;
}
.storage-entry-comp .description, .storage-entry-comp .readable-instant-comp {
-fx-padding: 0 0 0 0.1em;
}

View file

@ -1,18 +0,0 @@
.storage-entry-list-comp .content {
}
.list-mode.columns .column-name {
-fx-text-fill: grey;
}
.list-mode.columns {
-fx-padding: 0 0.2em 0.0em 0;
}
.storage-entry-list-comp .content-pane {
-fx-padding: 0.0em 0.1em 0.2em 0.2em;
}
.storage-entry-list-comp {
-fx-padding: 0 0.2em 0 0.2em;
}

View file

@ -1,68 +0,0 @@
.bar.storage-group-list-comp {
-fx-pref-width: 17em;
-fx-background-radius: 0 3px 3px 0;
-fx-border-width: 2px 2px 2px 0;
-fx-border-color: -xp-border-accent;
-fx-border-radius: 0 3px 3px 0;
-fx-padding: 0;
}
.storage-group-list-comp .text-field {
-fx-padding: 0.05em 0 0.1em 0;
-fx-text-fill: -xp-text-accent-base;
}
.storage-group-list-comp .icon-button-comp {
-fx-text-fill: -xp-text-accent-base;
}
/* Temp collection */
.storage-group-list-comp .temp.label {
-fx-padding: 0.05em 0 0.1em 0;
-fx-text-fill: -xp-text-accent-base;
}
.storage-group-list-comp .temp .ikonli-font-icon {
-fx-icon-color: -xp-text-accent-base;
}
.storage-group-list-comp .date {
-fx-text-fill: -xp-text-accent-light;
}
.storage-group-list-comp .count-comp {
-fx-text-fill: -xp-text-accent-light;
}
.storage-group-entry {
-fx-padding: 0.1em 0 0.1em 0;
}
.storage-group-entry .icon {
-fx-padding: 0.25em 0.4em 0.25em 0.25em;
}
.storage-group-list-comp .list-cell:filled {
-fx-background-radius: 0 3px 3px 0;
-fx-border-width: 0 0 1px 0;
-fx-border-color: #FFF5;
-fx-border-insets: 8 18 5 15;
-fx-border-radius: 10;
}
.storage-group-list-comp .list-cell:filled:selected {
-fx-background-color: #59c8ec77;
}
.list-cell:empty {
-fx-opacity: 0;
}
.storage-group-list-comp .list-cell {
-fx-padding: 0;
-fx-focus-color: transparent;
}
.store-list-comp .list-cell {
-fx-padding: 0;
}

View file

@ -70,7 +70,7 @@
-fx-background-color: derive(-color-neutral-muted, 25%);
}
.store-entry-comp:hover {
.store-entry-comp:hover, .store-entry-comp:focused {
-fx-background-color: -color-neutral-muted;
}
@ -82,7 +82,7 @@
-fx-opacity: 1.0;
}
.expand-button:hover {
.expand-button:hover,.expand-button:focused {
-fx-background-color: -color-neutral-muted;
}
@ -101,7 +101,7 @@
/* Section */
.store-entry-section-comp .separator {
-fx-padding: 0 0.75em 0 0.75em;
-fx-padding: 0 12px 0 35px;
-fx-border-insets: 0px;
}

View file

@ -22,7 +22,7 @@
-fx-background-color: transparent;
}
.store-section-mini-comp .item:hover {
.store-section-mini-comp .item:hover, .store-section-mini-comp .item:focused {
-fx-background-color: -color-accent-subtle;
}
@ -36,7 +36,7 @@
-fx-border-color: -color-border-subtle;
}
.store-section-mini-comp .expand-button:hover {
.store-section-mini-comp .expand-button:hover, .store-section-mini-comp .expand-button:focused {
-fx-background-color: -color-neutral-muted;
}

View file

@ -13,8 +13,8 @@
-fx-padding: 1.2em;
}
.layout {
.store-layout .split-pane-divider {
-fx-background-color: transparent;
}
.root:dark.background {
@ -34,7 +34,7 @@
-fx-border-color: -color-neutral-emphasis;
}
.edit-button.icon-button-comp:hover {
.edit-button.icon-button-comp:hover, .edit-button.icon-button-comp:focused {
-fx-background-color: -color-accent-muted;
}
@ -68,3 +68,11 @@
-fx-padding: 0;
}
.root:light .loading-comp {
-fx-background-color: rgba(100,100,100,0.5);
}
.root:dark .loading-comp {
-fx-background-color: rgba(0,0,0,0.5);
}

View file

@ -1,8 +1,8 @@
package io.xpipe.core.charsetter;
import io.xpipe.core.store.FileStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.FailableConsumer;
import io.xpipe.core.util.FailableSupplier;
import lombok.Value;
import java.io.BufferedReader;
@ -76,7 +76,7 @@ public abstract class Charsetter {
}
public abstract Result read(
FailableSupplier<InputStream, Exception> in, FailableConsumer<InputStreamReader, Exception> con)
FailableSupplier<InputStream> in, FailableConsumer<InputStreamReader, Exception> con)
throws Exception;
public Result detect(StreamDataStore store) throws Exception {
@ -107,13 +107,13 @@ public abstract class Charsetter {
}
}
if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof ShellStore m) {
if (result.getNewLine() == null) {
result = new Result(
result.getCharset(),
m.getShellType() != null ? m.getShellType().getNewLine() : null);
}
}
// if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof ShellStore m) {
// if (result.getNewLine() == null) {
// result = new Result(
// result.getCharset(),
// m.getShellType() != null ? m.getShellType().getNewLine() : null);
// }
// }
if (result.getCharset() == null) {
result = new Result(StreamCharset.UTF8, result.getNewLine());
@ -167,17 +167,6 @@ public abstract class Charsetter {
return StandardCharsets.UTF_8;
}
@FunctionalInterface
public interface FailableSupplier<R, E extends Throwable> {
R get() throws E;
}
@FunctionalInterface
public interface FailableConsumer<T, E extends Throwable> {
void accept(T var1) throws E;
}
@Value
public static class Result {
StreamCharset charset;

View file

@ -1,6 +1,7 @@
package io.xpipe.core.dialog;
import io.xpipe.core.charsetter.Charsetter;
import io.xpipe.core.util.FailableConsumer;
import io.xpipe.core.util.FailableSupplier;
import io.xpipe.core.util.SecretValue;
@ -27,7 +28,7 @@ import java.util.function.Supplier;
*/
public abstract class Dialog {
private final List<Charsetter.FailableConsumer<?, Exception>> completion = new ArrayList<>();
private final List<FailableConsumer<?, Exception>> completion = new ArrayList<>();
protected Object eval;
private Supplier<?> evaluation;
@ -416,7 +417,7 @@ public abstract class Dialog {
return this;
}
public Dialog onCompletion(Charsetter.FailableConsumer<?, Exception> s) {
public Dialog onCompletion(FailableConsumer<?, Exception> s) {
completion.add(s);
return this;
}
@ -426,7 +427,7 @@ public abstract class Dialog {
return this;
}
public Dialog onCompletion(List<Charsetter.FailableConsumer<?, Exception>> s) {
public Dialog onCompletion(List<FailableConsumer<?, Exception>> s) {
completion.addAll(s);
return this;
}
@ -440,8 +441,8 @@ public abstract class Dialog {
public <T> void complete() throws Exception {
if (evaluation != null) {
eval = evaluation.get();
for (Charsetter.FailableConsumer<?, Exception> c : completion) {
Charsetter.FailableConsumer<T, Exception> ct = (Charsetter.FailableConsumer<T, Exception>) c;
for (FailableConsumer<?, Exception> c : completion) {
FailableConsumer<T, Exception> ct = (FailableConsumer<T, Exception>) c;
ct.accept((T) eval);
}
}

View file

@ -1,6 +1,6 @@
package io.xpipe.core.process;
import io.xpipe.core.charsetter.Charsetter;
import io.xpipe.core.util.FailableConsumer;
import io.xpipe.core.util.FailableFunction;
import java.io.InputStream;
@ -12,10 +12,11 @@ import java.util.function.Function;
public interface CommandControl extends ProcessControl {
int UNASSIGNED_EXIT_CODE = -1;
int EXIT_TIMEOUT_EXIT_CODE = -2;
int START_FAILED_EXIT_CODE = -3;
int INTERNAL_ERROR_EXIT_CODE = -4;
// Keep these out of a normal exit code range
int UNASSIGNED_EXIT_CODE = -80001;
int EXIT_TIMEOUT_EXIT_CODE = -80002;
int START_FAILED_EXIT_CODE = -80003;
int INTERNAL_ERROR_EXIT_CODE = -80004;
enum TerminalExitMode {
KEEP_OPEN,
@ -71,7 +72,7 @@ public interface CommandControl extends ProcessControl {
CommandControl exitTimeout(Integer timeout);
void withStdoutOrThrow(Charsetter.FailableConsumer<InputStreamReader, Exception> c);
void withStdoutOrThrow(FailableConsumer<InputStreamReader, Exception> c);
String readStdoutDiscardErr() throws Exception;
@ -85,6 +86,8 @@ public interface CommandControl extends ProcessControl {
String readStdoutOrThrow() throws Exception;
String readStdoutAndWait() throws Exception;
default boolean discardAndCheckExit() throws ProcessOutputException {
try {
discardOrThrow();

View file

@ -0,0 +1,6 @@
package io.xpipe.core.process;
public interface OsNameState {
String getOsName();
}

View file

@ -6,9 +6,21 @@ import java.io.BufferedReader;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class PropertiesFormatsParser {
@SneakyThrows
public static Map<String, String> parseLine(String line, String split, String quotes) {
var map = new LinkedHashMap<String, String>();
var pattern = Pattern.compile("(\\w+?)\\s*" + split + "\\s*" + quotes + "(.+?)" + quotes);
var matcher = pattern.matcher(line);
while (matcher.find()) {
map.put(matcher.group(1), matcher.group(2));
}
return map;
}
@SneakyThrows
public static Map<String, String> parse(String text, String split) {
var map = new LinkedHashMap<String, String>();

View file

@ -13,7 +13,7 @@ import lombok.extern.jackson.Jacksonized;
@Getter
@Jacksonized
@SuperBuilder
public class ShellStoreState extends DataStoreState {
public class ShellStoreState extends DataStoreState implements OsNameState {
OsType osType;
String osName;

View file

@ -21,7 +21,6 @@ public interface FileSystem extends Closeable, AutoCloseable {
@Value
@NonFinal
class FileEntry {
@NonNull
FileSystem fileSystem;
@NonNull
@ -39,7 +38,7 @@ public interface FileSystem extends Closeable, AutoCloseable {
FileKind kind;
public FileEntry(
@NonNull FileSystem fileSystem,
FileSystem fileSystem,
@NonNull String path,
Instant date,
boolean hidden,

View file

@ -1,13 +1,9 @@
package io.xpipe.core.store;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialect;
import java.nio.charset.Charset;
public interface ShellStore extends DataStore, InternalCacheDataStore, LaunchableStore, FileSystemStore, ValidatableStore {
public interface ShellStore extends DataStore, LaunchableStore, FileSystemStore, ValidatableStore {
static boolean isLocal(ShellStore s) {
return s instanceof LocalStore;
@ -23,32 +19,8 @@ public interface ShellStore extends DataStore, InternalCacheDataStore, Launchabl
return control();
}
default ShellDialect getShellType() {
return getCache("type", ShellDialect.class, null);
}
default OsType getOsType() {
return getOrCompute("os", OsType.class, () -> {
try (var sc = control().start()) {
return sc.getOsType();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
}
default Charset getCharset() {
return getCache("charset", Charset.class, null);
}
ShellControl control();
default ShellDialect determineType() throws Exception {
try (var pc = control().start()) {
return pc.getShellDialect();
}
}
@Override
default void validate() throws Exception {
var c = control();
@ -58,11 +30,4 @@ public interface ShellStore extends DataStore, InternalCacheDataStore, Launchabl
try (ShellControl pc = c.start()) {}
}
default String queryMachineName() throws Exception {
try (var pc = control().start()) {
var operatingSystem = pc.getOsType();
return operatingSystem.determineOperatingSystemName(pc);
}
}
}

43
dist/changelogs/1.7.4.md vendored Normal file
View file

@ -0,0 +1,43 @@
## Changes in 1.7.4
### VMware support for desktop hypervisors
This update introduces an experimental implementation to support VMware virtual machines in VMware Player, Workstation, and Fusion installations.
The support includes actions like listing, starting, stopping, and pausing VMs plus opening a shell session or file browser session via SSH.
Note that the initial connection to a VM, which runs some setup, can take a long time.
It seems like the VMware CLI it is very slow in that regard, maybe I can find some improvements.
If everything works out well with this first attempt at VM support, it can be expanded to other hypervisors.
### Git storage for everyone
Up until now, the git storage functionality has only been available with a professional license.
However, due to the complex nature of git repositories, this feature had some inevitable rough edges
and did not live up to the robustness of a professional product.
As a result, I am moving this feature into the community edition.
### UI rework
Some parts of the UI have been reworked to achieve a more consistent appearance.
Furthermore, it has also been improved in regard to accessibility and its interaction with screen readers.
### Other changes
- The left sidebars in the connection overview and browser can now be persistently resized
- Implement various performance improvements
- When dragging files straight out of the browser, they now can also resolve to text output.
You can therefore now drag files into a terminal to quickly paste their file names for example.
- Rework connection creation to automatically preselect most commonly used type
- Fix browser exit race conditions
- Fix application not starting up when settings file was corrupted
- Fix connection getting stuck when shell did not support stderr. It will now just stop after a few seconds
- Fix application not starting up on Windows systems older than Windows 10
- Fix negative process exit codes being interpreted as internal errors and not shown
## Previous changes in 1.7
- [1.7.3](https://github.com/xpipe-io/xpipe/releases/tag/1.7.3)
- [1.7.2](https://github.com/xpipe-io/xpipe/releases/tag/1.7.2)
- [1.7.1](https://github.com/xpipe-io/xpipe/releases/tag/1.7.1)

View file

@ -1,177 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -1,4 +0,0 @@
name=Commons Codec
version=1.15
license=Apache License 2.0
link=https://commons.apache.org/proper/commons-code/

View file

@ -1,177 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -1,4 +0,0 @@
name=Commons Collections
version=4.4
license=Apache License 2.0
link=https://commons.apache.org/proper/commons-collections/

View file

@ -1,177 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -1,4 +0,0 @@
name=Commons Exec
version=1.3
license=Apache License 2.0
link=https://commons.apache.org/proper/commons-exec/

View file

@ -1,28 +0,0 @@
Copyright (c) 2005, Graph Builder
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
-Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
-Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
-Neither the name of Graph Builder nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,4 +0,0 @@
name=curvesapi
version=1.07
license=BSD 3-Clause
link=https://github.com/virtuald/curvesapi

View file

@ -1,23 +0,0 @@
Copyright (c) 2014, TomasMikula
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,4 +0,0 @@
name=Flowless
version=0.6.6
license=BSD 2-Clause
link=https://github.com/FXMisc/Flowless

Some files were not shown because too many files have changed in this diff Show more