Каков кроссплатформенный способ получения пути к локальному каталогу данных приложения?
Мне нужен независимый от платформы способ получения пути к локальному каталогу данных приложения. System.getenv("LOCALAPPDATA")
Кажется, работает только с Windows. Как мне это сделать?
8 ответов
Вы могли бы, вероятно, сказать что-то вроде (противоречить мне, если я ошибаюсь, или если это плохой подход)
private String workingDirectory;
//here, we assign the name of the OS, according to Java, to a variable...
private String OS = (System.getProperty("os.name")).toUpperCase();
//to determine what the workingDirectory is.
//if it is some version of Windows
if (OS.contains("WIN"))
{
//it is simply the location of the "AppData" folder
workingDirectory = System.getenv("AppData");
}
//Otherwise, we assume Linux or Mac
else
{
//in either case, we would start in the user's home directory
workingDirectory = System.getProperty("user.home");
//if we are on a Mac, we are not done, we look for "Application Support"
workingDirectory += "/Library/Application Support";
}
//we are now free to set the workingDirectory to the subdirectory that is our
//folder.
Обратите внимание, что в этом коде я в полной мере использую преимущества '/'
такой же как '\\'
при работе с каталогами. Windows использует '\\'
как pathSeparator, но доволен '/'
, тоже. (По крайней мере, Windows 7 есть.) Она также нечувствительна к регистру переменных среды; мы могли бы так же легко сказать workingDirectory = System.getenv("APPDATA");
и это сработало бы так же хорошо.
Лично я нашел appdirs
быть очень полезным для подобных случаев использования. Он имеет функции, которые находят различные виды полезных каталогов:
getUserDataDir
getUserConfigDir
getUserCacheDir
getUserLogDir
getSiteDataDir
← похоже, это тот, который вам нуженgetSiteConfigDir
Места, которые он возвращает, более или менее стандартны:
- В Unix это соответствует спецификации XDG Base Directory.
- В Windows он вызывает SHGetFolderPath.
- В macOS он использует хорошо прописанные пути.
Задание старое, но мне не хватает разнообразных вариантов ответов, а не забавных абсолютных путей. Я ничего не знаю о OSX. Этот пост содержит только информацию о Windows и Linux.
У меня недостаточно очков, чтобы расширить уже существующий ответ, поэтому я должен написать новый.
Linux: Как упоминалось ранее, существует нечто вроде freedesktop.org, который определяет стандарт, который пытаются выполнить дистрибутивы Linux. Существует также подстраница, определяющая переменные среды и их значения по умолчанию (если они не установлены, они по умолчанию пусты. Приложение должно сопоставить переменную со значением по умолчанию). Ссылка на эту страницу: freedesktop.org env vars
Vars определены соответствующие для этого вопроса:
- $ XDG_DATA_HOME (локальный) (по умолчанию: $ HOME /.local / share)
- $ XDG_CONFIG_HOME (local) (по умолчанию: $ HOME /.config)
- $ XDG_DATA_DIRS (global) (по умолчанию: / usr / local / share / или / usr / share /)
- $ XDG_CONFIG_DIRS (global) (по умолчанию: / etc / xdg)
Windows XP:
% APPDATA% (по умолчанию: C: \ Documents and Settings {имя пользователя} \ Application Data)
% CommonProgramFiles% (по умолчанию: C: \ Program Files \ Common Files) (общие программные файлы)
% CommonProgramFiles (x86)% (по умолчанию: C: \ Program Files (x86) \ Common Files) (только 64-разрядные!) (Общие программные файлы)
% ProgramFiles% (по умолчанию: % SystemDrive% \ Program Files)
% ProgramFiles (x86)% (по умолчанию: % SystemDrive% \ Program Files (x86) (только в 64-разрядной версии)) (только 64-разрядная!)
Windows Vista +:
- % APPDATA% (по умолчанию: C: \ Users {имя пользователя} \ AppData \ Roaming) (Совместно используется между связанными рабочими станциями. Пользователь локальный. Сохранение файлов и конфигураций)
- % LOCALAPPDATA% (по умолчанию: C: \ Users {имя пользователя} \ AppData \ Local) (Пользователь локальный. Сохранение файлов и конфигураций)
- % CommonProgramFiles% (по умолчанию: C: \ Program Files \ Common Files) (общие программные файлы)
% CommonProgramFiles (x86)% (по умолчанию: C: \ Program Files (x86) \ Common Files) (только 64-разрядные!) (Общие программные файлы)
% ProgramFiles% (по умолчанию: % SystemDrive% \ Program Files) (статические данные, которые не изменятся после установки)
% ProgramFiles (x86)% (по умолчанию: % SystemDrive% \ Program Files (x86) (только в 64-разрядной версии)) (только 64-разрядная версия!) (Статические данные, которые не изменятся после установки)
% ProgramData% (по умолчанию: % SystemDrive% \ ProgramData) (изменяемые данные, влияющие на всех пользователей)
Вкратце: в Linux есть две переменные окружения, которые не могут быть установлены (одна для конфигов, одна для файлов). Насколько мне известно, в Windows есть только одна переменная окружения для конфигов и файлов. Пожалуйста, используйте их вместо абсолютных путей.
Это кульминация различных других ответов StackOverflow наряду с некоторыми идеями из проекта appdata без введения зависимости от JNA. Существует зависимость от Apache Commons Lang3, которую можно устранить, используя возвращаемые значения изSystem.getProperty( "os.name" )
как показано в другом месте.
Код
Обратите внимание, что я не тестировал это ни на одной платформе, кроме Linux:
import java.io.FileNotFoundException;
import java.nio.file.Path;
import static java.lang.System.getProperty;
import static java.lang.System.getenv;
import static org.apache.commons.lang3.SystemUtils.*;
/**
* Responsible for determining the directory to write application data, across
* multiple platforms. See also:
*
* <ul>
* <li>
* <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">
* Linux: XDG Base Directory Specification
* </a>
* </li>
* <li>
* <a href="https://learn.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables">
* Windows: Recognized environment variables
* </a>
* </li>
* <li>
* <a href="https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html">
* MacOS: File System Programming Guide
* </a>
* </li>
* </ul>
* </p>
*/
public final class UserDataDir {
private static final Path UNDEFINED = Path.of( "/" );
private static final String PROP_USER_HOME = getProperty( "user.home" );
private static final String PROP_OS_VERSION = getProperty( "os.version" );
private static final String ENV_APPDATA = getenv( "AppData" );
private static final String ENV_XDG_DATA_HOME = getenv( "XDG_DATA_HOME" );
private UserDataDir() { }
public static Path getAppPath( final String appName )
throws FileNotFoundException {
final var osPath = isWindows()
? getWinAppPath()
: isMacOs()
? getMacAppPath()
: isUnix()
? getUnixAppPath()
: UNDEFINED;
final var path = osPath.equals( UNDEFINED )
? getDefaultAppPath( appName )
: osPath.resolve( appName );
return ensureExists( path ) ? path : fail( path );
}
private static Path fail( final Path path ) throws FileNotFoundException {
throw new FileNotFoundException( path.toString() );
}
private static Path getWinAppPath() {
return ENV_APPDATA == null || ENV_APPDATA.isBlank()
? home( getWinVerAppPath() )
: Path.of( ENV_APPDATA );
}
/**
* Gets the application path with respect to the Windows version.
*
* @return The directory name paths relative to the user's home directory.
*/
private static String[] getWinVerAppPath() {
return PROP_OS_VERSION.startsWith( "5." )
? new String[]{"Application Data"}
: new String[]{"AppData", "Roaming"};
}
private static Path getMacAppPath() {
final var path = home( "Library", "Application Support" );
return ensureExists( path ) ? path : UNDEFINED;
}
private static Path getUnixAppPath() {
// Fallback in case the XDG data directory is undefined.
var path = home( ".local", "share" );
if( ENV_XDG_DATA_HOME != null && !ENV_XDG_DATA_HOME.isBlank() ) {
final var xdgPath = Path.of( ENV_XDG_DATA_HOME );
path = ensureExists( xdgPath ) ? xdgPath : path;
}
return path;
}
/**
* Returns a hidden directory relative to the user's home directory.
*
* @param appName The application name.
* @return A suitable directory for storing application files.
*/
private static Path getDefaultAppPath( final String appName ) {
return home( '.' + appName );
}
private static Path home( final String... paths ) {
return Path.of( PROP_USER_HOME, paths );
}
/**
* Verifies whether the path exists or was created.
*
* @param path The directory to verify.
* @return {@code true} if the path already exists or was created,
* {@code false} if the directory doesn't exist and couldn't be created.
*/
private static boolean ensureExists( final Path path ) {
final var file = path.toFile();
return file.exists() || file.mkdirs();
}
private static boolean isWindows() {
return IS_OS_WINDOWS;
}
private static boolean isMacOs() {
return IS_OS_MAC;
}
private static boolean isUnix() {
return IS_OS_UNIX;
}
}
Тест
Модульный тест, показывающий использование:
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import static org.junit.jupiter.api.Assertions.*;
class UserDataDirTest {
@Test
void test_Unix_GetAppDirectory_DirectoryExists()
throws FileNotFoundException {
final var path = UserDataDir.getAppPath( "test" );
final var file = path.toFile();
assertTrue( file.exists() );
assertTrue( file.delete() );
assertFalse( file.exists() );
}
}
Для этого не существует кроссплатформенного способа, потому что концепции, используемые различными ОС, слишком различны, чтобы "абстрагироваться". Я не знаком с соглашениями *nix и Mac, но в Windows нет "домашней папки", и приложение должно указать, хочет ли оно хранить данные в перемещаемом профиле (C:\Users\<username>\AppData\Roaming\<application vendor>\<application name>\
по умолчанию) или локальный профиль (C:\Users\<username>\AppData\Local\<application vendor>\<application name>\
по умолчанию).
Обратите внимание, что вы не можете жестко закодировать эти пути, потому что при сетевой установке они могут быть где-то еще. Вы также не должны полагаться на переменные среды, потому что они могут быть изменены пользователем. Ваше приложение должно вызвать функцию SHGetKnownFolderPath Windows API.
Разница между ними заключается в том, что локальный профиль зависит от пользователя и компьютера, а роуминговый профиль - от пользователя, поэтому в настройках, подобных моему университету, приложения, помещаемые в перемещаемый профиль, загружаются на сервер и синхронизируется с тем компьютером, на котором я вхожу.
Приложения должны нести ответственность за выбор локальных или роуминговых настроек. К сожалению, Java не позволяет приложениям решать это. Вместо этого есть глобальный настраиваемый пользователем параметр, который определяет, какую папку вы получите.
Проблема в том, что другие операционные системы даже не имеют четко определенного понятия "каталог данных приложения". Обычно это просто скрытый подкаталог в домашнем каталоге пользователя с условным именем, которое может быть или не быть именем приложения.
Механическая улитка комментирует так:
Не правда. У Linux есть один (~/.local по умолчанию), и я считаю, что OS X также делает.
Во-первых, это не ~/.local
, это ~/.local/share
, (Или, по крайней мере, на моей машине с Linux).
Во-вторых, это новая идея. Похоже, что он происходит от народа "freedesktop.org" через спецификацию XDG Base Directory. В других, более широко признанных спецификациях о том, как должны быть организованы файловые системы Linux / UNIX, не упоминается. И обратите внимание, что они говорят о своих "стандартах" на этой странице: http://www.freedesktop.org/wiki/
Наконец, эта идея не реализована большинством команд Linux. Это довольно ненаучно, но, глядя на скрытые каталоги на моем компьютере с Linux, я вижу признаки как минимум 40 различных приложений, использующих ~/
или пользовательский подкаталог. В отличие от этого есть признаки только 16 приложений в ~/.local/share
,
Соглашение об именах, которое реализуется менее чем 1/3 приложениями, вряд ли является "четко определенной концепцией" ... и, конечно, не таким способом, который позволял бы находить каталог данных произвольного приложения переносимым способом.
Вы можете использовать это
String currentDir = new File(".").getAbsolutePath();
или это:
System.getProperty("user.dir")
Я предпочитаю первый вариант
С уважением