Dépôt officiel du code source de l'ERP OpenConcerto
Rev 144 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
/**
* A set of base directories.
*
* @author Sylvain CUAZ
*/
public abstract class BaseDirs {
static private final String DATA = "Data";
static private final String PREFERENCES = "Preferences";
static private final String CACHES = "Caches";
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
static public final class XDG extends BaseDirs {
protected XDG(final ProductInfo info, final String subdir) {
super(info, subdir);
}
private String getHomePath() {
// ATTN on FreeBSD man 8 service : "service command sets HOME to /" thus one should
// probably not use this class (perhaps Portable).
return StringUtils.coalesce(System.getenv("HOME"), System.getProperty("user.home"));
}
private String getInHomePath(final String subPath) {
final String homePath = getHomePath();
// If no home, use current working directory
return homePath == null ? subPath : homePath + '/' + subPath;
}
@Override
protected File _getAppDataFolder() {
/*
* $XDG_DATA_HOME defines the base directory relative to which user specific data files
* should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
* $HOME/.local/share should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_DATA_HOME"), getInHomePath(".local/share")), this.getAppID());
}
@Override
protected File _getPreferencesFolder() {
/*
* $XDG_CONFIG_HOME defines the base directory relative to which user specific
* configuration files should be stored. If $XDG_CONFIG_HOME is either not set or empty,
* a default equal to $HOME/.config should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_CONFIG_HOME"), getInHomePath(".config")), this.getAppID());
}
@Override
protected File _getCacheFolder() {
/*
* $XDG_CACHE_HOME defines the base directory relative to which user specific
* non-essential data files should be stored. If $XDG_CACHE_HOME is either not set or
* empty, a default equal to $HOME/.cache should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_CACHE_HOME"), getInHomePath(".cache")), this.getAppID());
}
}
static public final class Unknown extends BaseDirs {
protected Unknown(final ProductInfo info, final String subdir) {
super(info, subdir);
}
}
static public final class Windows extends BaseDirs {
private final String path;
protected Windows(final ProductInfo info, final String subdir) {
super(info, subdir);
final String orgID = info.getOrganizationName() == null ? null : FileUtils.sanitize(info.getOrganizationName());
final String appID = this.getAppName();
// handle missing org and avoid OpenConcerto/OpenConcerto
this.path = orgID == null || orgID.equals(appID) ? appID : orgID + File.separatorChar + appID;
// ProductInfo test emptiness
assert this.path.charAt(0) != File.separatorChar && this.path.charAt(this.path.length() - 1) != File.separatorChar : "Separator not in between : " + this.path;
}
protected final String getPath() {
return this.path;
}
@Override
protected File _getAppDataFolder() {
// do not use LOCALAPPDATA as the user needs its data synchronised
return new File(System.getenv("APPDATA"), this.getPath() + File.separatorChar + DATA);
}
@Override
protected File _getPreferencesFolder() {
// do not use LOCALAPPDATA as configuration should be small enough to be synchronised on
// the network
return new File(System.getenv("APPDATA"), this.getPath() + File.separatorChar + PREFERENCES);
}
@Override
protected File _getCacheFolder() {
// use LOCALAPPDATA as caches can be quite big and don't need to be synchronised
return new File(System.getenv("LOCALAPPDATA"), this.getPath() + File.separatorChar + CACHES);
}
}
// https://developer.apple.com/library/mac/qa/qa1170/_index.html
static public final class Mac extends BaseDirs {
protected Mac(final ProductInfo info, final String subdir) {
super(info, subdir);
}
@Override
protected File _getAppDataFolder() {
// NOTE : "Application Support" directory is reserved for non-essential application
// resources
return new File(System.getProperty("user.home") + "/Library/" + this.getAppName());
}
@Override
protected File _getPreferencesFolder() {
return new File(System.getProperty("user.home") + "/Library/Preferences/" + this.getAppFullID());
}
@Override
protected File _getCacheFolder() {
return new File(System.getProperty("user.home") + "/Library/Caches/" + this.getAppFullID());
}
}
static public final class Portable extends BaseDirs {
private final File rootDir;
protected Portable(final File rootDir, final ProductInfo info, final String subdir) {
super(info, subdir);
this.rootDir = rootDir;
}
public final File getRootDir() {
return this.rootDir;
}
@Override
protected File _getAppDataFolder() {
return new File(this.getRootDir(), DATA);
}
@Override
protected File _getPreferencesFolder() {
return new File(this.getRootDir(), PREFERENCES);
}
@Override
protected File _getCacheFolder() {
return new File(this.getRootDir(), CACHES);
}
}
public static final BaseDirs createPortable(final File rootDir, final ProductInfo info, final String subdir) {
return new Portable(rootDir, info, subdir);
}
public static final BaseDirs create(final ProductInfo info) {
return create(info, null);
}
public static final BaseDirs create(final ProductInfo info, final String subdir) {
final OSFamily os = OSFamily.getInstance();
if (os == OSFamily.Windows)
return new Windows(info, subdir);
else if (os == OSFamily.Mac)
return new Mac(info, subdir);
else if (os instanceof OSFamily.Unix)
return new XDG(info, subdir);
else
return new Unknown(info, subdir);
}
private final ProductInfo info;
private final String subdir;
protected BaseDirs(final ProductInfo info, final String subdir) {
this.info = info;
this.subdir = subdir == null ? null : FileUtils.sanitize(subdir);
}
// should use other methods to avoid invalid characters
private final ProductInfo getInfo() {
return this.info;
}
protected final String getAppName() {
return FileUtils.sanitize(this.getInfo().getName());
}
protected final String getAppID() {
return this.getInfo().getID();
}
protected final String getAppFullID() {
final String res = this.getInfo().getFullID();
return res != null ? res : this.getAppID();
}
static public final File getFolderToWrite(final File dir) throws IOException {
// MAYBE test for symlink
if (dir.isDirectory() && dir.canWrite())
return dir;
if (dir.exists())
throw new IOException((dir.isDirectory() ? "Not writable: " : "Not a directory: ") + dir);
// create with 0700 mode (from § Referencing this specification)
final String perms = "rwx------";
try {
Files.createDirectories(dir.toPath(), PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(perms)));
} catch (UnsupportedOperationException e) {
// e.g. this is Windows
Files.createDirectories(dir.toPath());
FileUtils.setFilePermissionsFromPOSIX(dir, perms);
}
return dir;
}
protected final File getSubDir(final File dir) {
return this.subdir == null ? dir : new File(dir, this.subdir);
}
protected File _getAppDataFolder() {
return new File(System.getProperty("user.home"), "." + this.getAppFullID());
}
// where to write user-hidden data files (e.g. mbox files, DB files)
public final File getAppDataFolder() {
return getSubDir(_getAppDataFolder());
}
public final File getAppDataFolderToWrite() throws IOException {
return getFolderToWrite(this.getAppDataFolder());
}
protected File _getPreferencesFolder() {
return this._getAppDataFolder();
}
// where to write configuration
public final File getPreferencesFolder() {
return getSubDir(_getPreferencesFolder());
}
public final File getPreferencesFolderToWrite() throws IOException {
return getFolderToWrite(this.getPreferencesFolder());
}
protected File _getCacheFolder() {
return new File(System.getProperty("java.io.tmpdir"), this.getAppFullID());
}
// where to write data that can be re-created
public final File getCacheFolder() {
return getSubDir(_getCacheFolder());
}
public final File getCacheFolderToWrite() throws IOException {
return getFolderToWrite(this.getCacheFolder());
}
protected File _getStateFolder() {
return this._getCacheFolder();
}
// where to write data that is non-essential but cannot be recreated
// - logfiles
// - state of application windows on exit
// - recently opened files
// See STATE directory in https://wiki.debian.org/XDGBaseDirectorySpecification
public final File getStateFolder() {
return getSubDir(_getStateFolder());
}
public final File getStateFolderToWrite() throws IOException {
return getFolderToWrite(this.getStateFolder());
}
@Override
public String toString() {
return BaseDirs.class.getSimpleName() + " " + this.getClass().getSimpleName();
}
public static void main(String[] args) throws IOException {
final String appName = args.length > 0 ? args[0] : "fooApp";
final String companyName = args.length > 1 ? args[1] : "acme";
final String subdir = System.getProperty("subdir");
final BaseDirs instance = create(new ProductInfo(CollectionUtils.createMap(ProductInfo.ORGANIZATION_NAME, companyName, ProductInfo.NAME, appName)), subdir);
System.out.println(instance);
System.out.println("app data : " + instance.getAppDataFolder());
System.out.println("preferences : " + instance.getPreferencesFolder());
System.out.println("cache : " + instance.getCacheFolder());
// test creation and permission
if (Boolean.getBoolean("createCacheDir")) {
final File createdCache = instance.getCacheFolderToWrite();
System.out.println("cache dir created : " + createdCache);
if (Boolean.getBoolean("deleteCacheDir")) {
Files.delete(createdCache.toPath());
System.out.println("cache dir deleted");
}
}
}
}