001/* 002 * VM-Operator 003 * Copyright (C) 2023 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.vmoperator.util; 020 021import java.io.IOException; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.util.List; 025import java.util.Optional; 026 027/** 028 * Utilities to access configurable file system directories. Based on 029 * the [FHS](https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html) and the 030 * [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). 031 */ 032@SuppressWarnings("PMD.UseUtilityClass") 033public class FsdUtils { 034 035 /** 036 * Adds a directory with the user's name to the path. 037 * If such a directory does not exist yet, creates it. 038 * If this file or the directory is not writable, 039 * return the given path. 040 * 041 * @param path the path 042 * @return the path 043 */ 044 public static Path addUser(Path path) { 045 String user = System.getProperty("user.name"); 046 if (user == null) { 047 return path; 048 } 049 Path dir = path.resolve(user); 050 if (Files.exists(dir)) { 051 return dir; 052 } 053 try { 054 Files.createDirectories(dir); 055 } catch (IOException e) { // NOPMD 056 // Just trying, doesn't matter 057 } 058 if (!Files.isWritable(dir)) { 059 return path; 060 } 061 return dir; 062 } 063 064 /** 065 * Returns the directory for temporary storage. 066 * 067 * @return the path 068 */ 069 public static Path tmpDir() { 070 return Path.of(System.getProperty("java.io.tmpdir", "/tmp")); 071 } 072 073 /** 074 * Returns the real home directory of the user or, if not 075 * available, a sub directory in {@link #tmpDir()} with 076 * the user's name. 077 * 078 * @return the path 079 */ 080 public static Path userHome() { 081 Path home = Optional.ofNullable(System.getProperty("user.home")) 082 .map(Path::of).orElse(null); 083 if (home != null) { 084 return home; 085 } 086 return addUser(tmpDir()); 087 } 088 089 /** 090 * Returns the data home. 091 * 092 * @param appName the application name 093 * @return the path 094 */ 095 public static Path dataHome(String appName) { 096 return Optional.ofNullable(System.getenv().get("XDG_DATA_HOME")) 097 .map(Path::of).orElse(userHome().resolve(".local").resolve("share")) 098 .resolve(appName); 099 } 100 101 /** 102 * Returns the config home. 103 * 104 * @param appName the application name 105 * @return the path 106 */ 107 public static Path configHome(String appName) { 108 return Optional.ofNullable(System.getenv().get("XDG_CONFIG_HOME")) 109 .map(Path::of).orElse(userHome().resolve(".config")) 110 .resolve(appName); 111 } 112 113 /** 114 * Returns the state directory. 115 * 116 * @param appName the application name 117 * @return the path 118 */ 119 public static Path stateHome(String appName) { 120 return Optional.ofNullable(System.getenv().get("XDG_STATE_HOME")) 121 .map(Path::of) 122 .orElse(userHome().resolve(".local").resolve("state")) 123 .resolve(appName); 124 } 125 126 /** 127 * Returns the runtime directory. 128 * 129 * @param appName the application name 130 * @return the path 131 */ 132 public static Path runtimeDir(String appName) { 133 return Optional.ofNullable(System.getenv("XDG_RUNTIME_DIR")) 134 .map(Path::of).orElseGet(() -> { 135 var runtimeBase = Path.of("/run"); 136 var dir = addUser(runtimeBase); 137 if (!dir.equals(runtimeBase)) { 138 return dir; 139 } 140 return addUser(tmpDir()); 141 }).resolve(appName); 142 } 143 144 /** 145 * Find a configuration file. The given filename is searched for in: 146 * 147 * 1. the current working directory, 148 * 1. the {@link #configHome(String)} 149 * 1. the subdirectory `appName` of `/etc/opt` 150 * 1. the subdirectory `appName` of `/etc` 151 * 152 * @param appName the application name 153 * @param filename the filename 154 * @return the optional 155 */ 156 public static Optional<Path> findConfigFile(String appName, 157 String filename) { 158 var candidates = List.of(Path.of(filename), 159 configHome(appName).resolve(filename), 160 Path.of("/etc").resolve("opt").resolve(appName).resolve(filename), 161 Path.of("/etc").resolve(appName).resolve(filename)); 162 for (var candidate : candidates) { 163 if (Files.exists(candidate)) { 164 return Optional.of(candidate); 165 } 166 } 167 return Optional.empty(); 168 } 169 170}