--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -24,65 +24,26 @@
*/
package jdk.jpackage.internal;
-import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.InvalidPathException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import javax.imageio.ImageIO;
-import javax.xml.stream.XMLOutputFactory;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
+import static jdk.jpackage.internal.DesktopIntegration.*;
import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
-import static jdk.jpackage.internal.LinuxAppImageBuilder.DEFAULT_ICON;
-import static jdk.jpackage.internal.LinuxAppImageBuilder.ICON_PNG;
import static jdk.jpackage.internal.StandardBundlerParam.*;
abstract class LinuxPackageBundler extends AbstractBundler {
- private static final String DESKTOP_COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL";
- private static final String DESKTOP_COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL";
- private static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS";
-
- private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
- new StandardBundlerParam<>(
- "linux.app.bundler",
- LinuxAppBundler.class,
- (params) -> new LinuxAppBundler(),
- null
- );
-
- private static final BundlerParamInfo<String> MENU_GROUP =
- new StandardBundlerParam<>(
- Arguments.CLIOptions.LINUX_MENU_GROUP.getId(),
- String.class,
- params -> I18N.getString("param.menu-group.default"),
- (s, p) -> s
- );
-
- private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
- new StandardBundlerParam<>(
- Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
- Boolean.class,
- params -> false,
- (s, p) -> (s == null || "null".equalsIgnoreCase(s))
- ? false : Boolean.valueOf(s)
- );
-
LinuxPackageBundler(BundlerParamInfo<String> packageName) {
this.packageName = packageName;
}
- private final BundlerParamInfo<String> packageName;
- private boolean withFindNeededPackages;
-
@Override
final public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
@@ -361,449 +322,16 @@
}
}
- /**
- * Helper to create files for desktop integration.
- */
- private class DesktopIntegration {
-
- DesktopIntegration(PlatformPackage thePackage,
- Map<String, ? super Object> params) {
-
- associations = FILE_ASSOCIATIONS.fetchFrom(params).stream().filter(
- a -> {
- if (a == null) {
- return false;
- }
- List<String> mimes = FA_CONTENT_TYPE.fetchFrom(a);
- return (mimes != null && !mimes.isEmpty());
- }).collect(Collectors.toUnmodifiableList());
-
- launchers = ADD_LAUNCHERS.fetchFrom(params);
-
- this.thePackage = thePackage;
-
- customIconFile = ICON_PNG.fetchFrom(params);
-
- verbose = VERBOSE.fetchFrom(params);
- resourceDir = RESOURCE_DIR.fetchFrom(params);
-
- // XDG recommends to use vendor prefix in desktop file names as xdg
- // commands copy files to system directories.
- // Package name should be a good prefix.
- final String desktopFileName = String.format("%s-%s.desktop",
- thePackage.name(), APP_NAME.fetchFrom(params));
- final String mimeInfoFileName = String.format("%s-%s-MimeInfo.xml",
- thePackage.name(), APP_NAME.fetchFrom(params));
-
- mimeInfoFile = new DesktopFile(mimeInfoFileName);
-
- if (!associations.isEmpty() || SHORTCUT_HINT.fetchFrom(params) || customIconFile != null) {
- //
- // Create primary .desktop file if one of conditions is met:
- // - there are file associations configured
- // - user explicitely requested to create a shortcut
- // - custom icon specified
- //
- desktopFile = new DesktopFile(desktopFileName);
- iconFile = new DesktopFile(String.format("%s.png",
- APP_NAME.fetchFrom(params)));
- } else {
- desktopFile = null;
- iconFile = null;
- }
-
- desktopFileData = Collections.unmodifiableMap(
- createDataForDesktopFile(params));
-
- nestedIntegrations = launchers.stream().map(
- launcherParams -> new DesktopIntegration(thePackage,
- launcherParams)).collect(Collectors.toList());
- }
-
- List<String> requiredPackages() {
- return Stream.of(List.of(this), nestedIntegrations).flatMap(
- List::stream).map(DesktopIntegration::requiredPackagesSelf).flatMap(
- List::stream).distinct().collect(Collectors.toList());
- }
-
- Map<String, String> create() throws IOException {
- if (iconFile != null) {
- // Create application icon file.
- prepareSrcIconFile();
- }
-
- Map<String, String> data = new HashMap<>(desktopFileData);
-
- final ShellCommands shellCommands;
- if (desktopFile != null) {
- // Create application desktop description file.
- createDesktopFile(data);
-
- // Shell commands will be created only if desktop file
- // should be installed.
- shellCommands = new ShellCommands();
- } else {
- shellCommands = null;
- }
-
- if (!associations.isEmpty()) {
- // Create XML file with mime types corresponding to file associations.
- createFileAssociationsMimeInfoFile();
-
- shellCommands.setFileAssociations();
-
- // Create icon files corresponding to file associations
- Map<String, Path> mimeTypeWithIconFile = createFileAssociationIconFiles();
- mimeTypeWithIconFile.forEach((k, v) -> {
- shellCommands.addIcon(k, v);
- });
- }
-
- // Create shell commands to install/uninstall integration with desktop of the app.
- if (shellCommands != null) {
- shellCommands.applyTo(data);
- }
-
- boolean needCleanupScripts = !associations.isEmpty();
-
- // Take care of additional launchers if there are any.
- // Process every additional launcher as the main application launcher.
- // Collect shell commands to install/uninstall integration with desktop
- // of the additional launchers and append them to the corresponding
- // commands of the main launcher.
- List<String> installShellCmds = new ArrayList<>(Arrays.asList(
- data.get(DESKTOP_COMMANDS_INSTALL)));
- List<String> uninstallShellCmds = new ArrayList<>(Arrays.asList(
- data.get(DESKTOP_COMMANDS_UNINSTALL)));
- for (var integration: nestedIntegrations) {
- if (!integration.associations.isEmpty()) {
- needCleanupScripts = true;
- }
-
- Map<String, String> launcherData = integration.create();
-
- installShellCmds.add(launcherData.get(DESKTOP_COMMANDS_INSTALL));
- uninstallShellCmds.add(launcherData.get(
- DESKTOP_COMMANDS_UNINSTALL));
- }
-
- data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(
- installShellCmds));
- data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(
- uninstallShellCmds));
-
- if (needCleanupScripts) {
- // Pull in utils.sh scrips library.
- try (InputStream is = getResourceAsStream("utils.sh");
- InputStreamReader isr = new InputStreamReader(is);
- BufferedReader reader = new BufferedReader(isr)) {
- data.put(UTILITY_SCRIPTS, reader.lines().collect(
- Collectors.joining(System.lineSeparator())));
- }
- } else {
- data.put(UTILITY_SCRIPTS, "");
- }
-
- return data;
- }
-
- private List<String> requiredPackagesSelf() {
- if (desktopFile != null) {
- return List.of("xdg-utils");
- }
- return Collections.emptyList();
- }
-
- private Map<String, String> createDataForDesktopFile(
- Map<String, ? super Object> params) {
- Map<String, String> data = new HashMap<>();
- data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
- data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
- data.put("APPLICATION_ICON",
- iconFile != null ? iconFile.installPath().toString() : null);
- data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params));
- data.put("APPLICATION_LAUNCHER",
- thePackage.installedApplicationLayout().launchersDirectory().resolve(
- LinuxAppImageBuilder.getLauncherName(params)).toString());
-
- return data;
- }
-
- /**
- * Shell commands to integrate something with desktop.
- */
- private class ShellCommands {
-
- ShellCommands() {
- registerIconCmds = new ArrayList<>();
- unregisterIconCmds = new ArrayList<>();
-
- registerDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
- "install", desktopFile.installPath().toString());
- unregisterDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
- "uninstall", desktopFile.installPath().toString());
- }
-
- void setFileAssociations() {
- registerFileAssociationsCmd = String.join(" ", "xdg-mime",
- "install",
- mimeInfoFile.installPath().toString());
- unregisterFileAssociationsCmd = String.join(" ", "xdg-mime",
- "uninstall", mimeInfoFile.installPath().toString());
-
- //
- // Add manual cleanup of system files to get rid of
- // the default mime type handlers.
- //
- // Even after mime type is unregisterd with `xdg-mime uninstall`
- // command and desktop file deleted with `xdg-desktop-menu uninstall`
- // command, records in
- // `/usr/share/applications/defaults.list` (Ubuntu 16) or
- // `/usr/local/share/applications/defaults.list` (OracleLinux 7)
- // files remain referencing deleted mime time and deleted
- // desktop file which makes `xdg-mime query default` output name
- // of non-existing desktop file.
- //
- String cleanUpCommand = String.join(" ",
- "uninstall_default_mime_handler",
- desktopFile.installPath().getFileName().toString(),
- String.join(" ", getMimeTypeNamesFromFileAssociations()));
-
- unregisterFileAssociationsCmd = stringifyShellCommands(
- unregisterFileAssociationsCmd, cleanUpCommand);
- }
-
- void addIcon(String mimeType, Path iconFile) {
- final int imgSize = getSquareSizeOfImage(iconFile.toFile());
- final String dashMime = mimeType.replace('/', '-');
- registerIconCmds.add(String.join(" ", "xdg-icon-resource",
- "install", "--context", "mimetypes", "--size ",
- Integer.toString(imgSize), iconFile.toString(), dashMime));
- unregisterIconCmds.add(String.join(" ", "xdg-icon-resource",
- "uninstall", dashMime));
- }
+ private final BundlerParamInfo<String> packageName;
+ private boolean withFindNeededPackages;
+ private DesktopIntegration desktopIntegration;
- void applyTo(Map<String, String> data) {
- List<String> cmds = new ArrayList<>();
-
- cmds.add(registerDesktopFileCmd);
- cmds.add(registerFileAssociationsCmd);
- cmds.addAll(registerIconCmds);
- data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(cmds));
-
- cmds.clear();
- cmds.add(unregisterDesktopFileCmd);
- cmds.add(unregisterFileAssociationsCmd);
- cmds.addAll(unregisterIconCmds);
- data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(cmds));
- }
-
- private String registerDesktopFileCmd;
- private String unregisterDesktopFileCmd;
-
- private String registerFileAssociationsCmd;
- private String unregisterFileAssociationsCmd;
-
- private List<String> registerIconCmds;
- private List<String> unregisterIconCmds;
- }
-
- private final PlatformPackage thePackage;
-
- private final List<Map<String, ? super Object>> associations;
-
- private final List<Map<String, ? super Object>> launchers;
-
- /**
- * Desktop integration file. xml, icon, etc.
- * Resides somewhere in application installation tree.
- * Has two paths:
- * - path where it should be placed at package build time;
- * - path where it should be installed by package manager;
- */
- private class DesktopFile {
-
- DesktopFile(String fileName) {
- installPath = thePackage
- .installedApplicationLayout()
- .destktopIntegrationDirectory().resolve(fileName);
- srcPath = thePackage
- .sourceApplicationLayout()
- .destktopIntegrationDirectory().resolve(fileName);
- }
-
- private final Path installPath;
- private final Path srcPath;
-
- Path installPath() {
- return installPath;
- }
-
- Path srcPath() {
- return srcPath;
- }
- }
-
- private final boolean verbose;
- private final File resourceDir;
-
- private final DesktopFile mimeInfoFile;
- private final DesktopFile desktopFile;
- private final DesktopFile iconFile;
-
- final private List<DesktopIntegration> nestedIntegrations;
-
- private final Map<String, String> desktopFileData;
-
- /**
- * Path to icon file provided by user or null.
- */
- private final File customIconFile;
-
- private void appendFileAssociation(XMLStreamWriter xml,
- Map<String, ? super Object> assoc) throws XMLStreamException {
-
- xml.writeStartElement("mime-type");
- final String thisMime = FA_CONTENT_TYPE.fetchFrom(assoc).get(0);
- xml.writeAttribute("type", thisMime);
-
- final String description = FA_DESCRIPTION.fetchFrom(assoc);
- if (description != null && !description.isEmpty()) {
- xml.writeStartElement("comment");
- xml.writeCharacters(description);
- xml.writeEndElement();
- }
-
- final List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc);
- if (extensions == null) {
- Log.error(I18N.getString(
- "message.creating-association-with-null-extension"));
- } else {
- for (String ext : extensions) {
- xml.writeStartElement("glob");
- xml.writeAttribute("pattern", "*." + ext);
- xml.writeEndElement();
- }
- }
-
- xml.writeEndElement();
- }
-
- private void createFileAssociationsMimeInfoFile() throws IOException {
- XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+ private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
+ new StandardBundlerParam<>(
+ "linux.app.bundler",
+ LinuxAppBundler.class,
+ (params) -> new LinuxAppBundler(),
+ null
+ );
- try (Writer w = new BufferedWriter(new FileWriter(
- mimeInfoFile.srcPath().toFile()))) {
- XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
-
- xml.writeStartDocument();
- xml.writeStartElement("mime-info");
- xml.writeNamespace("xmlns",
- "https://www.freedesktop.org/standards/shared-mime-info");
-
- for (var assoc : associations) {
- appendFileAssociation(xml, assoc);
- }
-
- xml.writeEndElement();
- xml.writeEndDocument();
- xml.flush();
- xml.close();
-
- } catch (XMLStreamException ex) {
- Log.verbose(ex);
- throw new IOException(ex);
- }
- }
-
- private Map<String, Path> createFileAssociationIconFiles() throws
- IOException {
- Map<String, Path> mimeTypeWithIconFile = new HashMap<>();
- for (var assoc : associations) {
- File customFaIcon = FA_ICON.fetchFrom(assoc);
- if (customFaIcon == null || !customFaIcon.exists() || getSquareSizeOfImage(
- customFaIcon) == 0) {
- continue;
- }
-
- String fname = iconFile.srcPath().getFileName().toString();
- if (fname.indexOf(".") > 0) {
- fname = fname.substring(0, fname.lastIndexOf("."));
- }
-
- DesktopFile faIconFile = new DesktopFile(
- fname + "_fa_" + customFaIcon.getName());
-
- IOUtils.copyFile(customFaIcon, faIconFile.srcPath().toFile());
-
- mimeTypeWithIconFile.put(FA_CONTENT_TYPE.fetchFrom(assoc).get(0),
- faIconFile.installPath());
- }
- return mimeTypeWithIconFile;
- }
-
- private void createDesktopFile(Map<String, String> data) throws IOException {
- List<String> mimeTypes = getMimeTypeNamesFromFileAssociations();
- data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes));
-
- // prepare desktop shortcut
- try (Writer w = Files.newBufferedWriter(desktopFile.srcPath())) {
- String content = preprocessTextResource(
- desktopFile.srcPath().getFileName().toString(),
- I18N.getString("resource.menu-shortcut-descriptor"),
- "template.desktop",
- data,
- verbose,
- resourceDir);
- w.write(content);
- }
- }
-
- private void prepareSrcIconFile() throws IOException {
- if (customIconFile == null || !customIconFile.exists()) {
- fetchResource(iconFile.srcPath().getFileName().toString(),
- I18N.getString("resource.menu-icon"),
- DEFAULT_ICON,
- iconFile.srcPath().toFile(),
- verbose,
- resourceDir);
- } else {
- fetchResource(iconFile.srcPath().getFileName().toString(),
- I18N.getString("resource.menu-icon"),
- customIconFile,
- iconFile.srcPath().toFile(),
- verbose,
- resourceDir);
- }
- }
-
- private List<String> getMimeTypeNamesFromFileAssociations() {
- return associations.stream().map(
- a -> FA_CONTENT_TYPE.fetchFrom(a).get(0)).collect(
- Collectors.toUnmodifiableList());
- }
- }
-
- private static int getSquareSizeOfImage(File f) {
- try {
- BufferedImage bi = ImageIO.read(f);
- if (bi.getWidth() == bi.getHeight()) {
- return bi.getWidth();
- }
- } catch (IOException e) {
- Log.verbose(e);
- }
- return 0;
- }
-
- private static String stringifyShellCommands(String ... commands) {
- return stringifyShellCommands(Arrays.asList(commands));
- }
-
- private static String stringifyShellCommands(List<String> commands) {
- return String.join(System.lineSeparator(), commands.stream().filter(
- s -> s != null && !s.isEmpty()).collect(Collectors.toList()));
- }
-
- private DesktopIntegration desktopIntegration;
}