]> nmode's Git Repositories - signal-cli/commitdiff
Allow registering new accounts on both live and staging environments
authorAsamK <asamk@gmx.de>
Sat, 11 Jun 2022 20:45:51 +0000 (22:45 +0200)
committerAsamK <asamk@gmx.de>
Sun, 12 Jun 2022 15:29:25 +0000 (17:29 +0200)
in the same config directory

graalvm-config-dir/reflect-config.json
lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStorage.java
lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java
src/main/java/org/asamk/signal/App.java

index 2c2c94f96df5d1b1762a35a6f23a5666a99256e6..54ed49f6377e645bc1ac76f836d05be4e8b37e57 100644 (file)
   "queryAllDeclaredMethods":true,
   "queryAllDeclaredConstructors":true,
   "methods":[
-    {"name":"<init>","parameterTypes":["java.util.List"] }, 
-    {"name":"accounts","parameterTypes":[] }
+    {"name":"<init>","parameterTypes":["java.util.List","java.lang.Integer"] }, 
+    {"name":"accounts","parameterTypes":[] }, 
+    {"name":"version","parameterTypes":[] }
   ]
 },
 {
   "queryAllDeclaredMethods":true,
   "queryAllDeclaredConstructors":true,
   "methods":[
-    {"name":"<init>","parameterTypes":["java.lang.String","java.lang.String","java.lang.String"] }, 
+    {"name":"<init>","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":[] }
index 3821a30639feed52e031e92f8b78a85ad719f058..f397b5348e769b2985723eddc6f38b405e9fc586 100644 (file)
@@ -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<String> getAllLocalAccountNumbers() {
+    public Set<String> 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;
index 7507c3397b50951bc093f2e6bb66b049746a1201..401079867e11663e5a806fd4b546f9dda314a837 100644 (file)
@@ -2,7 +2,7 @@ package org.asamk.signal.manager.storage.accounts;
 
 import java.util.List;
 
-public record AccountsStorage(List<Account> accounts) {
+public record AccountsStorage(List<Account> accounts, Integer version) {
 
-    public record Account(String path, String number, String uuid) {}
+    public record Account(String path, String environment, String number, String uuid) {}
 }
index ea2f0a1b5e9d70fd5a9043ae2f8233ed4248e785..d708a41cd43e2b5cce9ba1b45072cfe7ebeef6f4 100644 (file)
@@ -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<String> getAllNumbers() {
+    public synchronized Set<String> getAllNumbers() throws IOException {
         return readAccounts().stream()
                 .map(AccountsStorage.Account::number)
                 .filter(Objects::nonNull)
                 .collect(Collectors.toSet());
     }
 
-    public synchronized Set<AccountsStorage.Account> getAllAccounts() {
-        return readAccounts().stream().filter(a -> a.number() != null).collect(Collectors.toSet());
+    public synchronized Set<AccountsStorage.Account> 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<AccountsStorage.Account> readAccounts() {
+    private List<AccountsStorage.Account> 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<AccountsStorage.Account> 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);
+    }
 }
index 37f5feecaca15e89185004625d05eb26c3a81852..91af270d7cb38e7bff1f74ffcc60827fcf8d6ea4 100644 (file)
@@ -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<String> 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);
         }
     }