diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/FsdUtils.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/FsdUtils.java new file mode 100644 index 0000000..8789f50 --- /dev/null +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/FsdUtils.java @@ -0,0 +1,168 @@ +/* + * VM-Operator + * Copyright (C) 2023 Michael N. Lipp + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jdrupes.vmoperator.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +/** + * Utilities to access configurable file system directories. Based on + * the [FHS](https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html) and the + * [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). + */ +@SuppressWarnings("PMD.UseUtilityClass") +public class FsdUtils { + + /** + * Adds a directory with the user's name to the path. + * If such a directory does not exist yet, creates it. + * If this file or the directory is not writable, + * return the given path. + * + * @param path the path + * @return the path + */ + public static Path addUser(Path path) { + String user = System.getProperty("user.name"); + if (user == null) { + return path; + } + Path dir = path.resolve(user); + if (Files.exists(dir)) { + return dir; + } + try { + Files.createDirectories(dir); + } catch (IOException e) { // NOPMD + // Just trying, doesn't matter + } + if (!Files.isWritable(dir)) { + return path; + } + return dir; + } + + /** + * Returns the directory for temporary storage. + * + * @return the path + */ + public static Path tmpDir() { + return Path.of(System.getProperty("java.io.tmpdir", "/tmp")); + } + + /** + * Returns the real home directory of the user or, if not + * available, a sub directory in {@link #tmpDir()} with + * the user's name. + * + * @return the path + */ + public static Path userHome() { + Path home = Optional.ofNullable(System.getProperty("user.home")) + .map(Path::of).orElse(null); + if (home != null) { + return home; + } + return addUser(tmpDir()); + } + + /** + * Returns the data home. + * + * @param appName the application name + * @return the path + */ + public static Path dataHome(String appName) { + return Optional.ofNullable(System.getenv().get("XDG_DATA_HOME")) + .map(Path::of).orElse(userHome().resolve(".local").resolve("share")) + .resolve(appName); + } + + /** + * Returns the config home. + * + * @param appName the application name + * @return the path + */ + public static Path configHome(String appName) { + return Optional.ofNullable(System.getenv().get("XDG_CONFIG_HOME")) + .map(Path::of).orElse(userHome().resolve(".config")) + .resolve(appName); + } + + /** + * Returns the state directory. + * + * @param appName the application name + * @return the path + */ + public static Path stateHome(String appName) { + return Optional.ofNullable(System.getenv().get("XDG_STATE_HOME")) + .map(Path::of) + .orElse(userHome().resolve(".local").resolve("state")) + .resolve(appName); + } + + /** + * Returns the runtime directory. + * + * @param appName the application name + * @return the path + */ + public static Path runtimeDir(String appName) { + return Optional.ofNullable(System.getenv("XDG_RUNTIME_DIR")) + .map(Path::of).orElseGet(() -> { + var runtimeBase = Path.of("/run"); + var dir = addUser(runtimeBase); + if (!dir.equals(runtimeBase)) { + return dir; + } + return addUser(tmpDir()); + }).resolve(appName); + } + + /** + * Find a configuration file. The given filename is searched for in: + * + * 1. the current working directory, + * 1. the {@link #configHome(String)} + * 1. the subdirectory `appName` of `/etc` + * + * @param appName the application name + * @param filename the filename + * @return the optional + */ + public static Optional findConfigFile(String appName, + String filename) { + var candidates = List.of(Path.of(filename), + configHome(appName).resolve(filename), + Path.of("/etc").resolve(appName).resolve(filename)); + for (var candidate : candidates) { + if (Files.exists(candidate)) { + return Optional.of(candidate); + } + } + return Optional.empty(); + } + +}