blob: e584319f8b947932dcc1fff360fad56781159b47 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 SAP SE and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP SE - initial version
*******************************************************************************/
package org.eclipse.urischeme.internal.registration;
import static java.util.stream.Collectors.joining;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
/**
* Used to change the MimeType property of a Linux .desktop file. Adds handler
* entries for uri schemes like "x-scheme-handler/myScheme;". Can also remove
* schemes.
*/
public class DesktopFileWriter {
private static final String LINE_SEPARATOR = System.lineSeparator();
private static final String EQUAL_SIGN = "="; //$NON-NLS-1$
private static final String KEY_MIME_TYPE = "MimeType"; //$NON-NLS-1$
private static final String KEY_EXEC = "Exec"; //$NON-NLS-1$
private static final String EXEC_URI_PLACEHOLDER = " %u"; //$NON-NLS-1$
private Map<String, String> properties;
/**
* Creates an instance of the DekstopFileWriter. Throws an
* {@link IllegalStateException} if the given lines is not a .desktop file. E.g.
* no "[Desktop Entry]" at the beginning
*
* @param lines The lines of the .desktop file (e.g. read with java.nio.Files
*
* @throws IllegalStateException if lines cannot be understood as .desktop file
*/
public DesktopFileWriter(List<String> lines) {
properties = getProperties(lines);
}
/**
* Checks if the given scheme is registered in this .desktop file
*
* @param scheme that should be checked for registration
* @return true if scheme is in the value of the MimeType property of given
* .desktop file; false otherwise
*/
public boolean isRegistered(String scheme) {
Util.assertUriSchemeIsLegal(scheme);
String mimeType = properties.get(KEY_MIME_TYPE);
if (mimeType == null || mimeType.isEmpty()) {
return false;
}
return mimeType.contains(getHandlerPlusScheme(scheme));
}
/**
* Adds an entry "x-scheme-handler/givenScheme;" to the MimeType property of the
* .desktop file. Creates the MimeType property if not yet existing. Otherwise
* adds to the entry separated by ";".
*
* @param scheme The uri scheme which should be handled by the application
* mentioned in the .desktop file.
*
* @throws IllegalArgumentException if the given scheme contains illegal
* characters
*
* @see #removeScheme(String)
*
* @see <a href= "https://tools.ietf.org/html/rfc3986#section-3.1">Uniform
* Resource Identifier (URI): Generic Syntax</a>
*
*/
public void addScheme(String scheme) {
// check precondition
Util.assertUriSchemeIsLegal(scheme);
String handlerPlusScheme = getHandlerPlusScheme(scheme);
if (properties.containsKey(KEY_MIME_TYPE)) {
String mimeType = properties.get(KEY_MIME_TYPE);
if (!mimeType.contains(handlerPlusScheme)) {
mimeType += handlerPlusScheme;
properties.put(KEY_MIME_TYPE, mimeType);
}
} else {
properties.put(KEY_MIME_TYPE, handlerPlusScheme);
}
}
/**
* Removes the corresponding handler ("x-scheme-handler/givenScheme;") for the
* given scheme from the MimeType property of the .desktop file. Removes the
* MimeType property completely if it is empty after removal.
*
* @param scheme The uri scheme which should not be handled anymore by the
* application mentioned in the .desktop file.
*
* @throws IllegalArgumentException if the given scheme contains illegal
* characters
*
* @see #addScheme(String)
*
* @see <a href=
* "https://tools.ietf.org/html/rfc3986#section-3.1">https://tools.ietf.org/html/rfc3986#section-3.1</a>
*
*/
public void removeScheme(String scheme) {
Util.assertUriSchemeIsLegal(scheme);
if (properties.containsKey(KEY_MIME_TYPE)) {
String handlerPlusScheme = getHandlerPlusScheme(scheme);
String mimeType = properties.get(KEY_MIME_TYPE);
mimeType = mimeType.replace(handlerPlusScheme, ""); //$NON-NLS-1$
if (mimeType.isEmpty()) {
properties.remove(KEY_MIME_TYPE);
} else {
properties.put(KEY_MIME_TYPE, mimeType);
}
}
}
/**
* Returns a byte array with all the properties and the changed MimeType
* property.
*
* @return the new file content as byte[]
*/
public byte[] getResult() {
addUriPlaceholderToExecProperty();
Function<Entry<String, String>, String> toList = (Entry<String, String> e) -> {
if (e.getValue() == null) {
return e.getKey();
}
return String.join(EQUAL_SIGN, e.getKey(), e.getValue());
};
String result = this.properties.entrySet().stream() //
.map(toList) //
.collect(joining(LINE_SEPARATOR));
return result.getBytes();
}
/**
* Returns the minimal content for a .desktop file needed for registering mime
* types in Linux.<br>
*
* The caller can call {@link #DesktopFileWriter(List)} afterwards to create an
* instance.
*
* @param eclipseExecutableLocation the location of the eclipse executable in
* the file system, spaces will be escaped
* @param productName the name of the product as defined in the
* branding
*
* @return The minimal file content as list (one entry = one file line)
*/
public static List<String> getMinimalDesktopFileContent(String eclipseExecutableLocation, String productName) {
String executable = escapeSpaces(eclipseExecutableLocation);
return Arrays.asList(//
"[Desktop Entry]", //$NON-NLS-1$
"Name=" + productName, //$NON-NLS-1$
"Exec=" + executable, //$NON-NLS-1$
"NoDisplay=true", //$NON-NLS-1$
"Type=Application" //$NON-NLS-1$
);
}
private static String escapeSpaces(String path) {
return path.replace(" ", "\\ "); //$NON-NLS-1$ //$NON-NLS-2$
}
private static String unescapeSpaces(String path) {
return path.replace("\\ ", " "); //$NON-NLS-1$ //$NON-NLS-2$
}
private void addUriPlaceholderToExecProperty() {
if (this.properties.containsKey(KEY_EXEC)) {
String execValue = this.properties.get(KEY_EXEC);
if (!execValue.contains(EXEC_URI_PLACEHOLDER)) {
this.properties.put(KEY_EXEC, execValue + EXEC_URI_PLACEHOLDER);
}
}
}
private Map<String, String> getProperties(List<String> lines) {
assertLinesNotEmpty(lines);
LinkedHashMap<String, String> props = new LinkedHashMap<>(); // keeps order
for (String line : lines) {
if (line.contains(EQUAL_SIGN)) {
String[] split = line.split(EQUAL_SIGN);
props.put(split[0], split[1]);
} else {
props.put(line, null);
}
}
assertDesktopEntryPresent(props);
return props;
}
private void assertDesktopEntryPresent(Map<String, String> props) {
Iterator<Entry<String, String>> iterator = props.entrySet().iterator();
String firstLine = iterator.next().getKey();
if ("[Desktop Entry]".equals(firstLine) == false) { //$NON-NLS-1$
throw new IllegalStateException("File seems not to be a 'desktop' file"); //$NON-NLS-1$
}
}
private void assertLinesNotEmpty(List<?> lines) {
if (lines.isEmpty()) {
throw new IllegalStateException("inputStream is empty"); //$NON-NLS-1$
}
}
private String getHandlerPlusScheme(String scheme) {
return "x-scheme-handler/" + scheme + ";";//$NON-NLS-1$ //$NON-NLS-2$
}
/**
* @return the location of the Eclipse executable for the running Eclipse
*/
public String getExecutableLocation() {
String executableLocation = properties.get(KEY_EXEC);
executableLocation = executableLocation.replace(EXEC_URI_PLACEHOLDER, ""); //$NON-NLS-1$ // cut uri placeholder
return unescapeSpaces(executableLocation);
}
}