From aaa6412469c0af93a226ba805e6dbe74b4ea9773 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 11 Jun 2022 22:45:51 +0200 Subject: [PATCH] Allow registering new accounts on both live and staging environments in the same config directory --- graalvm-config-dir/reflect-config.json | 8 +- .../signal/manager/SignalAccountFiles.java | 17 ++- .../storage/accounts/AccountsStorage.java | 4 +- .../storage/accounts/AccountsStore.java | 128 ++++++++++++++---- src/main/java/org/asamk/signal/App.java | 10 +- 5 files changed, 134 insertions(+), 33 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 2c2c94f9..54ed49f6 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -914,8 +914,9 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, "methods":[ - {"name":"","parameterTypes":["java.util.List"] }, - {"name":"accounts","parameterTypes":[] } + {"name":"","parameterTypes":["java.util.List","java.lang.Integer"] }, + {"name":"accounts","parameterTypes":[] }, + {"name":"version","parameterTypes":[] } ] }, { @@ -924,7 +925,8 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, "methods":[ - {"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String"] }, + {"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, + {"name":"environment","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"path","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] } diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java index 3821a306..f397b534 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -41,14 +41,24 @@ public class SignalAccountFiles { this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent); this.userAgent = userAgent; this.trustNewIdentity = trustNewIdentity; - this.accountsStore = new AccountsStore(pathConfig.dataPath()); + this.accountsStore = new AccountsStore(pathConfig.dataPath(), serviceEnvironment, accountPath -> { + if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { + return null; + } + + try { + return SignalAccount.load(pathConfig.dataPath(), accountPath, false, trustNewIdentity); + } catch (Exception e) { + return null; + } + }); } - public Set getAllLocalAccountNumbers() { + public Set getAllLocalAccountNumbers() throws IOException { return accountsStore.getAllNumbers(); } - public MultiAccountManager initMultiAccountManager() { + public MultiAccountManager initMultiAccountManager() throws IOException { final var managers = accountsStore.getAllAccounts().parallelStream().map(a -> { try { return initManager(a.number(), a.path()); @@ -108,6 +118,7 @@ public class SignalAccountFiles { if (account.getServiceEnvironment() == null) { account.setServiceEnvironment(serviceEnvironment); + accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci()); } return manager; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStorage.java b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStorage.java index 7507c339..40107986 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStorage.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStorage.java @@ -2,7 +2,7 @@ package org.asamk.signal.manager.storage.accounts; import java.util.List; -public record AccountsStorage(List accounts) { +public record AccountsStorage(List accounts, Integer version) { - public record Account(String path, String number, String uuid) {} + public record Account(String path, String environment, String number, String uuid) {} } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java index ea2f0a1b..d708a41c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java @@ -3,6 +3,8 @@ package org.asamk.signal.manager.storage.accounts; import com.fasterxml.jackson.databind.ObjectMapper; import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.config.ServiceEnvironment; +import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.Utils; import org.asamk.signal.manager.util.IOUtils; import org.slf4j.Logger; @@ -29,39 +31,52 @@ import java.util.stream.Stream; public class AccountsStore { + private static final int MINIMUM_STORAGE_VERSION = 1; + private static final int CURRENT_STORAGE_VERSION = 2; private final static Logger logger = LoggerFactory.getLogger(AccountsStore.class); private final ObjectMapper objectMapper = Utils.createStorageObjectMapper(); private final File dataPath; + private final String serviceEnvironment; + private final AccountLoader accountLoader; - public AccountsStore(final File dataPath) throws IOException { + public AccountsStore( + final File dataPath, final ServiceEnvironment serviceEnvironment, final AccountLoader accountLoader + ) throws IOException { this.dataPath = dataPath; + this.serviceEnvironment = getServiceEnvironmentString(serviceEnvironment); + this.accountLoader = accountLoader; if (!getAccountsFile().exists()) { createInitialAccounts(); } } - public synchronized Set getAllNumbers() { + public synchronized Set getAllNumbers() throws IOException { return readAccounts().stream() .map(AccountsStorage.Account::number) .filter(Objects::nonNull) .collect(Collectors.toSet()); } - public synchronized Set getAllAccounts() { - return readAccounts().stream().filter(a -> a.number() != null).collect(Collectors.toSet()); + public synchronized Set getAllAccounts() throws IOException { + return readAccounts().stream() + .filter(a -> a.environment() == null || serviceEnvironment.equals(a.environment())) + .filter(a -> a.number() != null) + .collect(Collectors.toSet()); } - public synchronized String getPathByNumber(String number) { + public synchronized String getPathByNumber(String number) throws IOException { return readAccounts().stream() + .filter(a -> a.environment() == null || serviceEnvironment.equals(a.environment())) .filter(a -> number.equals(a.number())) .map(AccountsStorage.Account::path) .findFirst() .orElse(null); } - public synchronized String getPathByAci(ACI aci) { + public synchronized String getPathByAci(ACI aci) throws IOException { return readAccounts().stream() + .filter(a -> a.environment() == null || serviceEnvironment.equals(a.environment())) .filter(a -> aci.toString().equals(a.uuid())) .map(AccountsStorage.Account::path) .findFirst() @@ -70,15 +85,22 @@ public class AccountsStore { public synchronized void updateAccount(String path, String number, ACI aci) { updateAccounts(accounts -> accounts.stream().map(a -> { + if (a.environment() != null && !serviceEnvironment.equals(a.environment())) { + return a; + } + if (path.equals(a.path())) { - return new AccountsStorage.Account(a.path(), number, aci == null ? null : aci.toString()); + return new AccountsStorage.Account(a.path(), + serviceEnvironment, + number, + aci == null ? null : aci.toString()); } if (number != null && number.equals(a.number())) { - return new AccountsStorage.Account(a.path(), null, a.uuid()); + return new AccountsStorage.Account(a.path(), a.environment(), null, a.uuid()); } if (aci != null && aci.toString().equals(a.toString())) { - return new AccountsStorage.Account(a.path(), a.number(), null); + return new AccountsStorage.Account(a.path(), a.environment(), a.number(), null); } return a; @@ -87,14 +109,21 @@ public class AccountsStore { public synchronized String addAccount(String number, ACI aci) { final var accountPath = generateNewAccountPath(); - final var account = new AccountsStorage.Account(accountPath, number, aci == null ? null : aci.toString()); + final var account = new AccountsStorage.Account(accountPath, + serviceEnvironment, + number, + aci == null ? null : aci.toString()); updateAccounts(accounts -> { final var existingAccounts = accounts.stream().map(a -> { + if (a.environment() != null && !serviceEnvironment.equals(a.environment())) { + return a; + } + if (number != null && number.equals(a.number())) { - return new AccountsStorage.Account(a.path(), null, a.uuid()); + return new AccountsStorage.Account(a.path(), a.environment(), null, a.uuid()); } - if (aci != null && aci.toString().equals(a.toString())) { - return new AccountsStorage.Account(a.path(), a.number(), null); + if (aci != null && aci.toString().equals(a.uuid())) { + return new AccountsStorage.Account(a.path(), a.environment(), a.number(), null); } return a; @@ -105,7 +134,9 @@ public class AccountsStore { } public void removeAccount(final String accountPath) { - updateAccounts(accounts -> accounts.stream().filter(a -> !a.path().equals(accountPath)).toList()); + updateAccounts(accounts -> accounts.stream().filter(a -> !( + (a.environment() == null || serviceEnvironment.equals(a.environment())) && a.path().equals(accountPath) + )).toList()); } private String generateNewAccountPath() { @@ -123,8 +154,8 @@ public class AccountsStore { private void createInitialAccounts() throws IOException { final var legacyAccountPaths = getLegacyAccountPaths(); final var accountsStorage = new AccountsStorage(legacyAccountPaths.stream() - .map(number -> new AccountsStorage.Account(number, number, null)) - .toList()); + .map(number -> new AccountsStorage.Account(number, null, number, null)) + .toList(), CURRENT_STORAGE_VERSION); IOUtils.createPrivateDirectories(dataPath); var fileName = getAccountsFile(); @@ -152,15 +183,52 @@ public class AccountsStore { .collect(Collectors.toSet()); } - private List readAccounts() { + private List readAccounts() throws IOException { + final var pair = openFileChannel(getAccountsFile()); + try (final var fileChannel = pair.first(); final var lock = pair.second()) { + final var storage = readAccountsLocked(fileChannel); + + var accountsVersion = storage.version() == null ? 1 : storage.version(); + if (accountsVersion > CURRENT_STORAGE_VERSION) { + throw new IOException("Accounts file was created by a more recent version: " + accountsVersion); + } else if (accountsVersion < MINIMUM_STORAGE_VERSION) { + throw new IOException("Accounts file was created by a no longer supported older version: " + + accountsVersion); + } else if (accountsVersion < CURRENT_STORAGE_VERSION) { + return upgradeAccountsFile(fileChannel, storage, accountsVersion).accounts(); + } + return storage.accounts(); + } + } + + private AccountsStorage upgradeAccountsFile( + final FileChannel fileChannel, final AccountsStorage storage, final int accountsVersion + ) { try { - final var pair = openFileChannel(getAccountsFile()); - try (final var fileChannel = pair.first(); final var lock = pair.second()) { - return readAccountsLocked(fileChannel).accounts(); + List newAccounts = storage.accounts(); + if (accountsVersion < 2) { + // add environment field + newAccounts = newAccounts.stream().map(a -> { + if (a.environment() != null) { + return a; + } + try (final var account = accountLoader.loadAccountOrNull(a.path())) { + if (account == null || account.getServiceEnvironment() == null) { + return a; + } + return new AccountsStorage.Account(a.path(), + getServiceEnvironmentString(account.getServiceEnvironment()), + a.number(), + a.uuid()); + } + }).toList(); } - } catch (IOException e) { - logger.error("Failed to read accounts list", e); - return List.of(); + final var newStorage = new AccountsStorage(newAccounts, CURRENT_STORAGE_VERSION); + saveAccountsLocked(fileChannel, newStorage); + return newStorage; + } catch (Exception e) { + logger.warn("Failed to upgrade accounts file", e); + return storage; } } @@ -170,7 +238,7 @@ public class AccountsStore { try (final var fileChannel = pair.first(); final var lock = pair.second()) { final var accountsStorage = readAccountsLocked(fileChannel); final var newAccountsStorage = updater.apply(accountsStorage.accounts()); - saveAccountsLocked(fileChannel, new AccountsStorage(newAccountsStorage)); + saveAccountsLocked(fileChannel, new AccountsStorage(newAccountsStorage, CURRENT_STORAGE_VERSION)); } } catch (IOException e) { logger.error("Failed to update accounts list", e); @@ -209,4 +277,16 @@ public class AccountsStore { } return new Pair<>(fileChannel, lock); } + + private String getServiceEnvironmentString(final ServiceEnvironment serviceEnvironment) { + return switch (serviceEnvironment) { + case LIVE -> "LIVE"; + case STAGING -> "STAGING"; + }; + } + + public interface AccountLoader { + + SignalAccount loadAccountOrNull(String accountPath); + } } diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index 37f5feec..91af270d 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -46,6 +46,7 @@ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; +import java.util.Set; import static net.sourceforge.argparse4j.DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS; @@ -188,7 +189,12 @@ public class App { return; } - var accounts = signalAccountFiles.getAllLocalAccountNumbers(); + Set accounts = null; + try { + accounts = signalAccountFiles.getAllLocalAccountNumbers(); + } catch (IOException e) { + throw new IOErrorException("Failed to load local accounts file", e); + } if (accounts.size() == 0) { throw new UserErrorException("No local users found, you first need to register or link an account"); } else if (accounts.size() > 1) { @@ -299,6 +305,8 @@ public class App { ) throws CommandException { try (var multiAccountManager = signalAccountFiles.initMultiAccountManager()) { command.handleCommand(ns, multiAccountManager, outputWriter); + } catch (IOException e) { + throw new IOErrorException("Failed to load local accounts file", e); } } -- 2.50.1