diff --git a/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java b/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java index f7465fc..63230fa 100644 --- a/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java +++ b/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java @@ -1,5 +1,6 @@ package com.ts3client.chatlog; +import com.ts3client.config.AppDirs; import com.ts3client.text.BBCode; import com.ts3client.text.HtmlText; import com.ts3client.text.TextQuery; @@ -121,7 +122,7 @@ public final class ChatLogStore { } public static Path defaultRoot() { - return Path.of(System.getProperty("user.home", "."), ".ts3client", "chats"); + return AppDirs.chatLogs(); } public static String decodePathComponent(String value) { diff --git a/ts3-client/core/src/main/java/com/ts3client/config/AppDirs.java b/ts3-client/core/src/main/java/com/ts3client/config/AppDirs.java new file mode 100644 index 0000000..6fa8d7b --- /dev/null +++ b/ts3-client/core/src/main/java/com/ts3client/config/AppDirs.java @@ -0,0 +1,53 @@ +package com.ts3client.config; + +import java.io.File; +import java.nio.file.Path; + +/** + * Where the client keeps its own state. On the desktop that is {@code ~/.ts3jclient}, with chat + * logs in the TeamSpeak 3 client's {@code ~/.ts3client/chats} so both clients share them. A + * platform without a meaningful home directory (Android) calls {@link #init} once at startup, + * before any store is loaded. + */ +public final class AppDirs { + + private static volatile File profile; + private static volatile Path chatLogs; + + private AppDirs() { + } + + public static void init(File profileDir, Path chatLogDir) { + profile = profileDir; + chatLogs = chatLogDir; + } + + /** The directory holding all persistent client state (settings, identities, caches). */ + public static File profile() { + File dir = profile; + if (dir == null) { + dir = new File(System.getProperty("user.home", "."), ".ts3jclient"); + profile = dir; + } + return dir; + } + + public static File file(String name) { + return new File(profile(), name); + } + + /** Creates the profile directory if it does not exist yet; call before writing into it. */ + public static void createProfile() { + //noinspection ResultOfMethodCallIgnored + profile().mkdirs(); + } + + public static Path chatLogs() { + Path dir = chatLogs; + if (dir == null) { + dir = Path.of(System.getProperty("user.home", "."), ".ts3client", "chats"); + chatLogs = dir; + } + return dir; + } +} diff --git a/ts3-client/core/src/main/java/com/ts3client/config/AwayMessages.java b/ts3-client/core/src/main/java/com/ts3client/config/AwayMessages.java index 5f9208c..dbf4b50 100644 --- a/ts3-client/core/src/main/java/com/ts3client/config/AwayMessages.java +++ b/ts3-client/core/src/main/java/com/ts3client/config/AwayMessages.java @@ -13,8 +13,9 @@ import java.util.Properties; */ public final class AwayMessages { - private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient"); - private static final File FILE = new File(DIR, "away.properties"); + private static File file() { + return AppDirs.file("away.properties"); + } private final List entries = new ArrayList<>(); @@ -36,9 +37,9 @@ public final class AwayMessages { public static AwayMessages load() { AwayMessages m = new AwayMessages(); - if (!FILE.isFile()) return m.withDefaults(); + if (!file().isFile()) return m.withDefaults(); Properties p = new Properties(); - try (FileInputStream in = new FileInputStream(FILE)) { + try (FileInputStream in = new FileInputStream(file())) { p.load(in); } catch (Exception e) { return m.withDefaults(); @@ -58,11 +59,8 @@ public final class AwayMessages { p.setProperty("message." + i, entries.get(i)); } try { - if (!DIR.isDirectory()) { - //noinspection ResultOfMethodCallIgnored - DIR.mkdirs(); - } - try (FileOutputStream out = new FileOutputStream(FILE)) { + AppDirs.createProfile(); + try (FileOutputStream out = new FileOutputStream(file())) { p.store(out, "TS3J client away messages"); } } catch (Exception ignored) { diff --git a/ts3-client/core/src/main/java/com/ts3client/config/BanReasonPresets.java b/ts3-client/core/src/main/java/com/ts3client/config/BanReasonPresets.java index 4e398a6..a690cc7 100644 --- a/ts3-client/core/src/main/java/com/ts3client/config/BanReasonPresets.java +++ b/ts3-client/core/src/main/java/com/ts3client/config/BanReasonPresets.java @@ -14,8 +14,9 @@ import java.util.Properties; */ public final class BanReasonPresets { - private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient"); - private static final File FILE = new File(DIR, "banreasons.properties"); + private static File file() { + return AppDirs.file("banreasons.properties"); + } private final List entries = new ArrayList<>(); @@ -36,9 +37,9 @@ public final class BanReasonPresets { public static BanReasonPresets load() { BanReasonPresets presets = new BanReasonPresets(); - if (!FILE.isFile()) return presets; + if (!file().isFile()) return presets; Properties p = new Properties(); - try (FileInputStream in = new FileInputStream(FILE)) { + try (FileInputStream in = new FileInputStream(file())) { p.load(in); } catch (Exception e) { return presets; @@ -58,11 +59,8 @@ public final class BanReasonPresets { p.setProperty("reason." + i, entries.get(i)); } try { - if (!DIR.isDirectory()) { - //noinspection ResultOfMethodCallIgnored - DIR.mkdirs(); - } - try (FileOutputStream out = new FileOutputStream(FILE)) { + AppDirs.createProfile(); + try (FileOutputStream out = new FileOutputStream(file())) { p.store(out, "TS3J client ban reason presets"); } } catch (Exception ignored) { diff --git a/ts3-client/core/src/main/java/com/ts3client/config/Bookmarks.java b/ts3-client/core/src/main/java/com/ts3client/config/Bookmarks.java index f0572f8..6b2abff 100644 --- a/ts3-client/core/src/main/java/com/ts3client/config/Bookmarks.java +++ b/ts3-client/core/src/main/java/com/ts3client/config/Bookmarks.java @@ -13,8 +13,9 @@ import java.util.Properties; */ public final class Bookmarks { - private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient"); - private static final File FILE = new File(DIR, "bookmarks.properties"); + private static File file() { + return AppDirs.file("bookmarks.properties"); + } private final List entries = new ArrayList<>(); @@ -41,9 +42,9 @@ public final class Bookmarks { public static Bookmarks load() { Bookmarks b = new Bookmarks(); - if (!FILE.isFile()) return b; + if (!file().isFile()) return b; Properties p = new Properties(); - try (FileInputStream in = new FileInputStream(FILE)) { + try (FileInputStream in = new FileInputStream(file())) { p.load(in); } catch (Exception e) { return b; @@ -83,11 +84,8 @@ public final class Bookmarks { p.setProperty(prefix + "connectOnStartup", Boolean.toString(bm.connectOnStartup)); } try { - if (!DIR.isDirectory()) { - //noinspection ResultOfMethodCallIgnored - DIR.mkdirs(); - } - try (FileOutputStream out = new FileOutputStream(FILE)) { + AppDirs.createProfile(); + try (FileOutputStream out = new FileOutputStream(file())) { p.store(out, "TS3J client bookmarks"); } } catch (Exception ignored) { diff --git a/ts3-client/core/src/main/java/com/ts3client/config/IdentityStore.java b/ts3-client/core/src/main/java/com/ts3client/config/IdentityStore.java index f0d3161..fb94777 100644 --- a/ts3-client/core/src/main/java/com/ts3client/config/IdentityStore.java +++ b/ts3-client/core/src/main/java/com/ts3client/config/IdentityStore.java @@ -32,8 +32,6 @@ public final class IdentityStore { /** Security level new identities are generated with, matching the TS3 client's default. */ public static final int DEFAULT_SECURITY_LEVEL = 8; - private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient"); - private static final File IDENTITY_DIR = new File(DIR, "identities"); private final List entries = new ArrayList<>(); @@ -51,7 +49,7 @@ public final class IdentityStore { /** Loads all identities from disk, migrating a legacy single-identity file if needed. */ public static IdentityStore load(Settings settings) { IdentityStore store = new IdentityStore(); - File[] files = IDENTITY_DIR.listFiles((d, n) -> n.toLowerCase(Locale.ROOT).endsWith(".ini")); + File[] files = directory().listFiles((d, n) -> n.toLowerCase(Locale.ROOT).endsWith(".ini")); if (files != null) { for (File f : files) { IdentityEntry e = read(f); @@ -218,7 +216,7 @@ public final class IdentityStore { private IdentityEntry create(String name, LocalIdentity identity) throws IOException { String id = uniqueId(name); - File file = new File(IDENTITY_DIR, id + ".ini"); + File file = new File(directory(), id + ".ini"); write(identity, name, file); IdentityEntry entry = new IdentityEntry(id, name, file, identity); entries.add(entry); @@ -283,7 +281,7 @@ public final class IdentityStore { private boolean taken(String id) { if (byId(id) != null) return true; - return new File(IDENTITY_DIR, id + ".ini").exists(); + return new File(directory(), id + ".ini").exists(); } private void sort() { @@ -331,6 +329,6 @@ public final class IdentityStore { /** Directory the managed identity files live in. */ public static File directory() { - return IDENTITY_DIR; + return AppDirs.file("identities"); } } diff --git a/ts3-client/core/src/main/java/com/ts3client/config/Settings.java b/ts3-client/core/src/main/java/com/ts3client/config/Settings.java index 891a563..5e5be15 100644 --- a/ts3-client/core/src/main/java/com/ts3client/config/Settings.java +++ b/ts3-client/core/src/main/java/com/ts3client/config/Settings.java @@ -64,8 +64,9 @@ public final class Settings { } } - private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient"); - private static final File FILE = new File(DIR, "settings.properties"); + private static File file() { + return AppDirs.file("settings.properties"); + } private final Properties props = new Properties(); @@ -74,7 +75,7 @@ public final class Settings { public String nickname = System.getProperty("user.name", "TS3J User"); public String serverPassword = ""; /** Legacy single-identity file; only used to migrate into the identity store. */ - public String identityFile = new File(DIR, "identity.ini").getAbsolutePath(); + public String identityFile = AppDirs.file("identity.ini").getAbsolutePath(); /** Id of the identity used when a server doesn't select one of its own. */ public String defaultIdentityId = ""; @@ -185,8 +186,8 @@ public final class Settings { public static Settings load() { Settings s = new Settings(); try { - if (FILE.isFile()) { - try (FileInputStream in = new FileInputStream(FILE)) { + if (file().isFile()) { + try (FileInputStream in = new FileInputStream(file())) { s.props.load(in); } s.applyFromProps(); @@ -199,12 +200,9 @@ public final class Settings { public void save() { try { - if (!DIR.isDirectory()) { - //noinspection ResultOfMethodCallIgnored - DIR.mkdirs(); - } + AppDirs.createProfile(); writeToProps(); - try (FileOutputStream out = new FileOutputStream(FILE)) { + try (FileOutputStream out = new FileOutputStream(file())) { props.store(out, "TS3J Swing Client settings"); } } catch (Exception ignored) { @@ -225,11 +223,6 @@ public final class Settings { return soundVolume * masterVolume; } - /** The directory holding all persistent client state (settings, identities, caches). */ - public static File configDir() { - return DIR; - } - private void applyFromProps() { lastAddress = props.getProperty("lastAddress", lastAddress); nickname = props.getProperty("nickname", nickname); diff --git a/ts3-client/core/src/main/java/com/ts3client/contacts/ContactStore.java b/ts3-client/core/src/main/java/com/ts3client/contacts/ContactStore.java index 2a4832f..169ea5b 100644 --- a/ts3-client/core/src/main/java/com/ts3client/contacts/ContactStore.java +++ b/ts3-client/core/src/main/java/com/ts3client/contacts/ContactStore.java @@ -1,5 +1,6 @@ package com.ts3client.contacts; +import com.ts3client.config.AppDirs; import com.ts3client.teamspeak.TeamSpeakSettingsDb; import java.io.File; @@ -35,8 +36,9 @@ public final class ContactStore { void onContactsChanged(); } - private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient"); - private static final File FILE = new File(DIR, "contacts.txt"); + private static File file() { + return AppDirs.file("contacts.txt"); + } private final File file; private final Map byUid = new LinkedHashMap<>(); @@ -50,8 +52,9 @@ public final class ContactStore { /** Loads the list, seeding it from the TeamSpeak 3 client's database the first time round. */ public static ContactStore load() { - ContactStore store = new ContactStore(FILE); - if (FILE.isFile()) { + File file = file(); + ContactStore store = new ContactStore(file); + if (file.isFile()) { store.readFile(); } else { List imported = TeamSpeakSettingsDb.readContacts(); @@ -90,10 +93,7 @@ public final class ContactStore { sb.append(c.toRecord()); } try { - if (!DIR.isDirectory()) { - //noinspection ResultOfMethodCallIgnored - DIR.mkdirs(); - } + AppDirs.createProfile(); Files.writeString(file.toPath(), sb.toString(), StandardCharsets.UTF_8); } catch (IOException ignored) { } diff --git a/ts3-client/core/src/main/java/com/ts3client/hotkey/Hotkeys.java b/ts3-client/core/src/main/java/com/ts3client/hotkey/Hotkeys.java index 756a4cd..cfbcaa4 100644 --- a/ts3-client/core/src/main/java/com/ts3client/hotkey/Hotkeys.java +++ b/ts3-client/core/src/main/java/com/ts3client/hotkey/Hotkeys.java @@ -1,5 +1,7 @@ package com.ts3client.hotkey; +import com.ts3client.config.AppDirs; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -14,8 +16,9 @@ import java.util.Properties; */ public final class Hotkeys { - private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient"); - private static final File FILE = new File(DIR, "hotkeys.properties"); + private static File file() { + return AppDirs.file("hotkeys.properties"); + } private final List entries = Collections.synchronizedList(new ArrayList<>()); @@ -48,9 +51,9 @@ public final class Hotkeys { public static Hotkeys load() { Hotkeys h = new Hotkeys(); - if (!FILE.isFile()) return h; + if (!file().isFile()) return h; Properties p = new Properties(); - try (FileInputStream in = new FileInputStream(FILE)) { + try (FileInputStream in = new FileInputStream(file())) { p.load(in); } catch (Exception e) { return h; @@ -87,11 +90,8 @@ public final class Hotkeys { } } try { - if (!DIR.isDirectory()) { - //noinspection ResultOfMethodCallIgnored - DIR.mkdirs(); - } - try (FileOutputStream out = new FileOutputStream(FILE)) { + AppDirs.createProfile(); + try (FileOutputStream out = new FileOutputStream(file())) { p.store(out, "TS3J client hotkeys"); } } catch (Exception ignored) { diff --git a/ts3-client/core/src/main/java/com/ts3client/net/IconRepository.java b/ts3-client/core/src/main/java/com/ts3client/net/IconRepository.java index a64c65f..8995310 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/IconRepository.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/IconRepository.java @@ -1,6 +1,6 @@ package com.ts3client.net; -import com.ts3client.config.Settings; +import com.ts3client.config.AppDirs; import java.io.File; import java.io.IOException; @@ -43,7 +43,7 @@ public final class IconRepository { private final Fetcher fetcher; private final Runnable onIconLoaded; - private final File cacheDir = new File(Settings.configDir(), "icons"); + private final File cacheDir = new File(AppDirs.profile(), "icons"); private final Map icons = new ConcurrentHashMap<>(); private final Set pending = ConcurrentHashMap.newKeySet(); private final Set failed = ConcurrentHashMap.newKeySet(); diff --git a/ts3-client/core/src/main/java/com/ts3client/net/avatar/AvatarRepository.java b/ts3-client/core/src/main/java/com/ts3client/net/avatar/AvatarRepository.java index af0e252..53fa3cb 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/avatar/AvatarRepository.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/avatar/AvatarRepository.java @@ -1,6 +1,6 @@ package com.ts3client.net.avatar; -import com.ts3client.config.Settings; +import com.ts3client.config.AppDirs; import java.io.File; import java.io.IOException; @@ -47,7 +47,7 @@ public final class AvatarRepository { * avatar has arrived, so the view can repaint */ public AvatarRepository(Fetcher fetcher, Runnable onAvatarLoaded) { - this(fetcher, onAvatarLoaded, new File(Settings.configDir(), "avatars")); + this(fetcher, onAvatarLoaded, new File(AppDirs.profile(), "avatars")); } /** @param cacheDir where downloaded avatars are kept between sessions */ diff --git a/ts3-client/core/src/main/java/com/ts3client/sound/SoundNotifier.java b/ts3-client/core/src/main/java/com/ts3client/sound/SoundNotifier.java index 83aa69a..397083e 100644 --- a/ts3-client/core/src/main/java/com/ts3client/sound/SoundNotifier.java +++ b/ts3-client/core/src/main/java/com/ts3client/sound/SoundNotifier.java @@ -1,5 +1,6 @@ package com.ts3client.sound; +import com.ts3client.config.AppDirs; import com.ts3client.config.Settings; import java.io.File; @@ -48,7 +49,7 @@ public final class SoundNotifier { /** Re-scans the installed packs and selects the configured one. */ public void reload() { - available = SoundPacks.findAll(new File(Settings.configDir(), "sound"), settings.soundPackDir); + available = SoundPacks.findAll(new File(AppDirs.profile(), "sound"), settings.soundPackDir); pack = SoundPacks.select(available, settings.soundPack); } diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/IconTheme.java b/ts3-client/swing/src/main/java/com/ts3client/ui/IconTheme.java index c8a5ed7..299f063 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/IconTheme.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/IconTheme.java @@ -5,6 +5,7 @@ import com.github.weisj.jsvg.SVGDocument; import com.github.weisj.jsvg.parser.LoaderContext; import com.github.weisj.jsvg.parser.SVGLoader; import com.github.weisj.jsvg.view.ViewBox; +import com.ts3client.config.AppDirs; import com.ts3client.config.Settings; import com.ts3client.gfx.IconPack; import com.ts3client.gfx.IconPacks; @@ -70,7 +71,7 @@ public final class IconTheme { /** Re-scans the pack folders and activates the one the settings name. */ public void reload(Settings settings) { - packs = IconPacks.findAll(new File(Settings.configDir(), "gfx"), settings.iconPackDir); + packs = IconPacks.findAll(new File(AppDirs.profile(), "gfx"), settings.iconPackDir); fallback = IconPacks.fallback(packs); setPack(IconPacks.select(packs, settings.iconPack)); }