]> nmode's Git Repositories - signal-cli/commitdiff
Add SignalAccountFiles as a central entry point
authorAsamK <asamk@gmx.de>
Wed, 9 Feb 2022 18:07:23 +0000 (19:07 +0100)
committerAsamK <asamk@gmx.de>
Fri, 11 Feb 2022 20:03:54 +0000 (21:03 +0100)
19 files changed:
lib/src/main/java/org/asamk/signal/manager/LibSignalLogger.java
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java
lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java
lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java
lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/Context.java
lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java [new file with mode: 0644]
src/main/java/org/asamk/signal/App.java
src/main/java/org/asamk/signal/Main.java

index c14b0f13be92cb889d5c6941a22046bed20d2d66..ac686fc5c6b1bac33236b437f0e6eedcc05fed76 100644 (file)
@@ -9,7 +9,7 @@ public class LibSignalLogger implements SignalProtocolLogger {
 
     private final static Logger logger = LoggerFactory.getLogger("LibSignal");
 
-    public static void initLogger() {
+    static void initLogger() {
         SignalProtocolLoggerProvider.setProvider(new LibSignalLogger());
     }
 
index b7a97c469373c3ae4dab5d075f1dba416e35e439..9db509adf9460638dec1713b7fdb7c70bbb3ac0d 100644 (file)
@@ -1,6 +1,5 @@
 package org.asamk.signal.manager;
 
-import org.asamk.signal.manager.api.AccountCheckException;
 import org.asamk.signal.manager.api.AttachmentInvalidException;
 import org.asamk.signal.manager.api.Configuration;
 import org.asamk.signal.manager.api.Device;
@@ -12,7 +11,6 @@ import org.asamk.signal.manager.api.InvalidStickerException;
 import org.asamk.signal.manager.api.Message;
 import org.asamk.signal.manager.api.MessageEnvelope;
 import org.asamk.signal.manager.api.NotMasterDeviceException;
-import org.asamk.signal.manager.api.NotRegisteredException;
 import org.asamk.signal.manager.api.Pair;
 import org.asamk.signal.manager.api.RecipientIdentifier;
 import org.asamk.signal.manager.api.SendGroupMessageResults;
@@ -23,16 +21,12 @@ import org.asamk.signal.manager.api.StickerPackUrl;
 import org.asamk.signal.manager.api.TypingAction;
 import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.api.UpdateGroup;
-import org.asamk.signal.manager.config.ServiceConfig;
-import org.asamk.signal.manager.config.ServiceEnvironment;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
 import org.asamk.signal.manager.groups.LastGroupAdminException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
-import org.asamk.signal.manager.storage.SignalAccount;
-import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 import org.asamk.signal.manager.storage.recipients.Contact;
 import org.asamk.signal.manager.storage.recipients.Profile;
 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
@@ -51,45 +45,6 @@ import java.util.UUID;
 
 public interface Manager extends Closeable {
 
-    static Manager init(
-            String number,
-            File settingsPath,
-            ServiceEnvironment serviceEnvironment,
-            String userAgent,
-            TrustNewIdentity trustNewIdentity
-    ) throws IOException, NotRegisteredException, AccountCheckException {
-        var pathConfig = PathConfig.createDefault(settingsPath);
-
-        if (!SignalAccount.userExists(pathConfig.dataPath(), number)) {
-            throw new NotRegisteredException();
-        }
-
-        var account = SignalAccount.load(pathConfig.dataPath(), number, true, trustNewIdentity);
-
-        if (!account.isRegistered()) {
-            account.close();
-            throw new NotRegisteredException();
-        }
-
-        account.initDatabase();
-        final var serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
-
-        final var manager = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
-
-        try {
-            manager.checkAccountState();
-        } catch (IOException e) {
-            manager.close();
-            throw new AccountCheckException("Error while checking account " + account + ": " + e.getMessage(), e);
-        }
-
-        return manager;
-    }
-
-    static void initLogger() {
-        LibSignalLogger.initLogger();
-    }
-
     static boolean isValidNumber(final String e164Number, final String countryCode) {
         return PhoneNumberFormatter.isValidNumber(e164Number, countryCode);
     }
index c15f1612ff82c7e2aad096a4650e604a7e543c64..742be52017d1ab093b9f9d1f0f76eeba56d5319c 100644 (file)
@@ -45,6 +45,7 @@ import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
 import org.asamk.signal.manager.groups.LastGroupAdminException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.manager.helper.AccountFileUpdater;
 import org.asamk.signal.manager.helper.Context;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.storage.groups.GroupInfo;
@@ -99,6 +100,7 @@ class ManagerImpl implements Manager {
     private final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class);
 
     private SignalAccount account;
+    private final AccountFileUpdater accountFileUpdater;
     private final SignalDependencies dependencies;
     private final Context context;
 
@@ -114,13 +116,15 @@ class ManagerImpl implements Manager {
     ManagerImpl(
             SignalAccount account,
             PathConfig pathConfig,
+            AccountFileUpdater accountFileUpdater,
             ServiceEnvironmentConfig serviceEnvironmentConfig,
             String userAgent
     ) {
         this.account = account;
+        this.accountFileUpdater = accountFileUpdater;
 
         final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(),
-                account.getAccount(),
+                account.getNumber(),
                 account.getPassword(),
                 account.getDeviceId());
         final var sessionLock = new SignalSessionLock() {
@@ -142,7 +146,12 @@ class ManagerImpl implements Manager {
         final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath());
         final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath());
 
-        this.context = new Context(account, dependencies, avatarStore, attachmentStore, stickerPackStore);
+        this.context = new Context(account,
+                accountFileUpdater,
+                dependencies,
+                avatarStore,
+                attachmentStore,
+                stickerPackStore);
         this.context.getAccountHelper().setUnregisteredListener(this::close);
         this.context.getReceiveHelper().setAuthenticationFailureListener(this::close);
         this.context.getReceiveHelper().setCaughtUpWithOldMessagesListener(() -> {
@@ -168,7 +177,7 @@ class ManagerImpl implements Manager {
 
     @Override
     public String getSelfNumber() {
-        return account.getAccount();
+        return account.getNumber();
     }
 
     void checkAccountState() throws IOException {
@@ -179,7 +188,7 @@ class ManagerImpl implements Manager {
     public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
         final var canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
             try {
-                final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getAccount());
+                final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getNumber());
                 if (!canonicalizedNumber.equals(n)) {
                     logger.debug("Normalized number {} to {}.", n, canonicalizedNumber);
                 }
diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java b/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java
new file mode 100644 (file)
index 0000000..e5dc2c0
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.manager;
+
+public class ManagerLogger {
+
+    public static void initLogger() {
+        LibSignalLogger.initLogger();
+    }
+}
index 9f1cf2fd4035aa7d76e460d86f6664eb1ba8a1a3..15b6059487b3aeec9bcb28f4300d29932d7356e6 100644 (file)
@@ -1,58 +1,13 @@
 package org.asamk.signal.manager;
 
-import org.asamk.signal.manager.api.AccountCheckException;
-import org.asamk.signal.manager.api.NotRegisteredException;
-import org.asamk.signal.manager.config.ServiceEnvironment;
-import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
-import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
-
-import java.io.File;
 import java.io.IOException;
 import java.net.URI;
-import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 
 public interface MultiAccountManager extends AutoCloseable {
 
-    static MultiAccountManager init(
-            final File settingsPath,
-            final ServiceEnvironment serviceEnvironment,
-            final String userAgent,
-            final TrustNewIdentity trustNewIdentity
-    ) {
-        final var logger = LoggerFactory.getLogger(MultiAccountManager.class);
-        final var managers = getAllLocalAccountNumbers(settingsPath).stream().map(a -> {
-            try {
-                return Manager.init(a, settingsPath, serviceEnvironment, userAgent, trustNewIdentity);
-            } catch (NotRegisteredException | IOException | AccountCheckException e) {
-                logger.warn("Ignoring {}: {} ({})", a, e.getMessage(), e.getClass().getSimpleName());
-                return null;
-            }
-        }).filter(Objects::nonNull).toList();
-
-        return new MultiAccountManagerImpl(managers, settingsPath, serviceEnvironment, userAgent);
-    }
-
-    static List<String> getAllLocalAccountNumbers(File settingsPath) {
-        var pathConfig = PathConfig.createDefault(settingsPath);
-        final var dataPath = pathConfig.dataPath();
-        final var files = dataPath.listFiles();
-
-        if (files == null) {
-            return List.of();
-        }
-
-        return Arrays.stream(files)
-                .filter(File::isFile)
-                .map(File::getName)
-                .filter(file -> PhoneNumberFormatter.isValidNumber(file, null))
-                .toList();
-    }
-
     List<String> getAccountNumbers();
 
     List<Manager> getManagers();
index 4c67fd12883b392dac6ad9d9ebb1580f89264fb8..072e965b565cc7ecb56b712ae5aabcc77890397d 100644 (file)
@@ -1,10 +1,8 @@
 package org.asamk.signal.manager;
 
-import org.asamk.signal.manager.config.ServiceEnvironment;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
@@ -25,21 +23,12 @@ class MultiAccountManagerImpl implements MultiAccountManager {
     private final Set<Consumer<Manager>> onManagerRemovedHandlers = new HashSet<>();
     private final Set<Manager> managers = new HashSet<>();
     private final Map<URI, ProvisioningManager> provisioningManagers = new HashMap<>();
-    private final File settingsPath;
-    private final ServiceEnvironment serviceEnvironment;
-    private final String userAgent;
-
-    public MultiAccountManagerImpl(
-            final Collection<Manager> managers,
-            final File settingsPath,
-            final ServiceEnvironment serviceEnvironment,
-            final String userAgent
-    ) {
+    private final SignalAccountFiles signalAccountFiles;
+
+    public MultiAccountManagerImpl(final Collection<Manager> managers, final SignalAccountFiles signalAccountFiles) {
+        this.signalAccountFiles = signalAccountFiles;
         this.managers.addAll(managers);
         managers.forEach(m -> m.addClosedListener(() -> this.removeManager(m)));
-        this.settingsPath = settingsPath;
-        this.serviceEnvironment = serviceEnvironment;
-        this.userAgent = userAgent;
     }
 
     @Override
@@ -99,9 +88,9 @@ class MultiAccountManagerImpl implements MultiAccountManager {
     }
 
     @Override
-    public Manager getManager(final String account) {
+    public Manager getManager(final String number) {
         synchronized (managers) {
-            return managers.stream().filter(m -> m.getSelfNumber().equals(account)).findFirst().orElse(null);
+            return managers.stream().filter(m -> m.getSelfNumber().equals(number)).findFirst().orElse(null);
         }
     }
 
@@ -119,12 +108,12 @@ class MultiAccountManagerImpl implements MultiAccountManager {
     }
 
     private ProvisioningManager getNewProvisioningManager() {
-        return ProvisioningManager.init(settingsPath, serviceEnvironment, userAgent, this::addManager);
+        return signalAccountFiles.initProvisioningManager(this::addManager);
     }
 
     @Override
-    public RegistrationManager getNewRegistrationManager(String account) throws IOException {
-        return RegistrationManager.init(account, settingsPath, serviceEnvironment, userAgent, this::addManager);
+    public RegistrationManager getNewRegistrationManager(String number) throws IOException {
+        return signalAccountFiles.initRegistrationManager(number, this::addManager);
     }
 
     @Override
index 81834220bd0008dc5641e2b776f9111d058af4d5..09a329f56f50d03746f38c8443741ee6296fb3ee 100644 (file)
@@ -1,36 +1,13 @@
 package org.asamk.signal.manager;
 
 import org.asamk.signal.manager.api.UserAlreadyExistsException;
-import org.asamk.signal.manager.config.ServiceConfig;
-import org.asamk.signal.manager.config.ServiceEnvironment;
 
-import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.util.concurrent.TimeoutException;
-import java.util.function.Consumer;
 
 public interface ProvisioningManager {
 
-    static ProvisioningManager init(
-            File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
-    ) {
-        return init(settingsPath, serviceEnvironment, userAgent, null);
-    }
-
-    static ProvisioningManager init(
-            File settingsPath,
-            ServiceEnvironment serviceEnvironment,
-            String userAgent,
-            Consumer<Manager> newManagerListener
-    ) {
-        var pathConfig = PathConfig.createDefault(settingsPath);
-
-        final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
-
-        return new ProvisioningManagerImpl(pathConfig, serviceConfiguration, userAgent, newManagerListener);
-    }
-
     URI getDeviceLinkUri() throws TimeoutException, IOException;
 
     String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException;
index bfa5109174a1893ec3680858eee361ca52e441d8..e0f1084b92866051c0639e2aead33e20a66e3d2a 100644 (file)
@@ -20,6 +20,7 @@ import org.asamk.signal.manager.api.UserAlreadyExistsException;
 import org.asamk.signal.manager.config.ServiceConfig;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.accounts.AccountsStore;
 import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.slf4j.Logger;
@@ -47,6 +48,7 @@ class ProvisioningManagerImpl implements ProvisioningManager {
     private final ServiceEnvironmentConfig serviceEnvironmentConfig;
     private final String userAgent;
     private final Consumer<Manager> newManagerListener;
+    private final AccountsStore accountsStore;
 
     private final SignalServiceAccountManager accountManager;
     private final IdentityKeyPair tempIdentityKey;
@@ -57,12 +59,14 @@ class ProvisioningManagerImpl implements ProvisioningManager {
             PathConfig pathConfig,
             ServiceEnvironmentConfig serviceEnvironmentConfig,
             String userAgent,
-            final Consumer<Manager> newManagerListener
+            final Consumer<Manager> newManagerListener,
+            final AccountsStore accountsStore
     ) {
         this.pathConfig = pathConfig;
         this.serviceEnvironmentConfig = serviceEnvironmentConfig;
         this.userAgent = userAgent;
         this.newManagerListener = newManagerListener;
+        this.accountsStore = accountsStore;
 
         tempIdentityKey = KeyUtils.generateIdentityKeyPair();
         registrationId = KeyHelper.generateRegistrationId(false);
@@ -91,11 +95,23 @@ class ProvisioningManagerImpl implements ProvisioningManager {
     public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
         var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
         var number = ret.getNumber();
+        var aci = ret.getAci();
 
         logger.info("Received link information from {}, linking in progress ...", number);
 
-        if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) {
-            throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), number));
+        var accountPath = accountsStore.getPathByAci(aci);
+        if (accountPath == null) {
+            accountPath = accountsStore.getPathByNumber(number);
+        }
+        if (accountPath != null
+                && SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)
+                && !canRelinkExistingAccount(accountPath)) {
+            throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), accountPath));
+        }
+        if (accountPath == null) {
+            accountPath = accountsStore.addAccount(number, aci);
+        } else {
+            accountsStore.updateAccount(accountPath, number, aci);
         }
 
         var encryptedDeviceName = deviceName == null
@@ -115,8 +131,9 @@ class ProvisioningManagerImpl implements ProvisioningManager {
         SignalAccount account = null;
         try {
             account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
+                    accountPath,
                     number,
-                    ret.getAci(),
+                    aci,
                     password,
                     encryptedDeviceName,
                     deviceId,
@@ -127,7 +144,12 @@ class ProvisioningManagerImpl implements ProvisioningManager {
 
             ManagerImpl m = null;
             try {
-                m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
+                final var accountPathFinal = accountPath;
+                m = new ManagerImpl(account,
+                        pathConfig,
+                        (newNumber, newAci) -> accountsStore.updateAccount(accountPathFinal, newNumber, newAci),
+                        serviceEnvironmentConfig,
+                        userAgent);
                 account = null;
 
                 logger.debug("Refreshing pre keys");
@@ -162,10 +184,13 @@ class ProvisioningManagerImpl implements ProvisioningManager {
         }
     }
 
-    private boolean canRelinkExistingAccount(final String number) throws IOException {
+    private boolean canRelinkExistingAccount(final String accountPath) throws IOException {
         final SignalAccount signalAccount;
         try {
-            signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
+            signalAccount = SignalAccount.load(pathConfig.dataPath(),
+                    accountPath,
+                    false,
+                    TrustNewIdentity.ON_FIRST_USE);
         } catch (IOException e) {
             logger.debug("Account in use or failed to load.", e);
             return false;
@@ -177,7 +202,11 @@ class ProvisioningManagerImpl implements ProvisioningManager {
                 return false;
             }
 
-            final var m = new ManagerImpl(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent);
+            final var m = new ManagerImpl(signalAccount,
+                    pathConfig,
+                    (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
+                    serviceEnvironmentConfig,
+                    userAgent);
             try (m) {
                 m.checkAccountState();
             } catch (AuthorizationFailedException ignored) {
index f094ed92e9d125dca9652b2b60115f719dd53443..c1c318d94b218da60888def98ee3b3887805f5d6 100644 (file)
@@ -3,60 +3,12 @@ package org.asamk.signal.manager;
 import org.asamk.signal.manager.api.CaptchaRequiredException;
 import org.asamk.signal.manager.api.IncorrectPinException;
 import org.asamk.signal.manager.api.PinLockedException;
-import org.asamk.signal.manager.config.ServiceConfig;
-import org.asamk.signal.manager.config.ServiceEnvironment;
-import org.asamk.signal.manager.storage.SignalAccount;
-import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
-import org.asamk.signal.manager.util.KeyUtils;
-import org.whispersystems.libsignal.util.KeyHelper;
 
 import java.io.Closeable;
-import java.io.File;
 import java.io.IOException;
-import java.util.function.Consumer;
 
 public interface RegistrationManager extends Closeable {
 
-    static RegistrationManager init(
-            String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
-    ) throws IOException {
-        return init(number, settingsPath, serviceEnvironment, userAgent, null);
-    }
-
-    static RegistrationManager init(
-            String number,
-            File settingsPath,
-            ServiceEnvironment serviceEnvironment,
-            String userAgent,
-            Consumer<Manager> newManagerListener
-    ) throws IOException {
-        var pathConfig = PathConfig.createDefault(settingsPath);
-
-        final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
-        if (!SignalAccount.userExists(pathConfig.dataPath(), number)) {
-            var identityKey = KeyUtils.generateIdentityKeyPair();
-            var registrationId = KeyHelper.generateRegistrationId(false);
-
-            var profileKey = KeyUtils.createProfileKey();
-            var account = SignalAccount.create(pathConfig.dataPath(),
-                    number,
-                    identityKey,
-                    registrationId,
-                    profileKey,
-                    TrustNewIdentity.ON_FIRST_USE);
-
-            return new RegistrationManagerImpl(account,
-                    pathConfig,
-                    serviceConfiguration,
-                    userAgent,
-                    newManagerListener);
-        }
-
-        var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
-
-        return new RegistrationManagerImpl(account, pathConfig, serviceConfiguration, userAgent, newManagerListener);
-    }
-
     void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException;
 
     void verifyAccount(
index c48a2e9915d3a4fbb363467f52a8d89dcf7379bc..81602122158f4abe94ea7f8ae2a1ebfab6da7c81 100644 (file)
@@ -21,6 +21,7 @@ import org.asamk.signal.manager.api.IncorrectPinException;
 import org.asamk.signal.manager.api.PinLockedException;
 import org.asamk.signal.manager.config.ServiceConfig;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
+import org.asamk.signal.manager.helper.AccountFileUpdater;
 import org.asamk.signal.manager.helper.PinHelper;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.util.Utils;
@@ -59,16 +60,19 @@ class RegistrationManagerImpl implements RegistrationManager {
 
     private final SignalServiceAccountManager accountManager;
     private final PinHelper pinHelper;
+    private final AccountFileUpdater accountFileUpdater;
 
     RegistrationManagerImpl(
             SignalAccount account,
             PathConfig pathConfig,
             ServiceEnvironmentConfig serviceEnvironmentConfig,
             String userAgent,
-            Consumer<Manager> newManagerListener
+            Consumer<Manager> newManagerListener,
+            AccountFileUpdater accountFileUpdater
     ) {
         this.account = account;
         this.pathConfig = pathConfig;
+        this.accountFileUpdater = accountFileUpdater;
         this.serviceEnvironmentConfig = serviceEnvironmentConfig;
         this.userAgent = userAgent;
         this.newManagerListener = newManagerListener;
@@ -82,7 +86,7 @@ class RegistrationManagerImpl implements RegistrationManager {
         this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
                 new DynamicCredentialsProvider(
                         // Using empty UUID, because registering doesn't work otherwise
-                        null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
+                        null, account.getNumber(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
                 userAgent,
                 groupsV2Operations,
                 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
@@ -101,7 +105,7 @@ class RegistrationManagerImpl implements RegistrationManager {
             try {
                 final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
                         new DynamicCredentialsProvider(account.getAci(),
-                                account.getAccount(),
+                                account.getNumber(),
                                 account.getPassword(),
                                 account.getDeviceId()),
                         userAgent,
@@ -120,7 +124,11 @@ class RegistrationManagerImpl implements RegistrationManager {
                 account.setRegistered(true);
                 logger.info("Reactivated existing account, verify is not necessary.");
                 if (newManagerListener != null) {
-                    final var m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
+                    final var m = new ManagerImpl(account,
+                            pathConfig,
+                            accountFileUpdater,
+                            serviceEnvironmentConfig,
+                            userAgent);
                     account = null;
                     newManagerListener.accept(m);
                 }
@@ -187,11 +195,13 @@ class RegistrationManagerImpl implements RegistrationManager {
         }
 
         //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
-        account.finishRegistration(ACI.parseOrNull(response.getUuid()), masterKey, pin);
+        final var aci = ACI.parseOrNull(response.getUuid());
+        account.finishRegistration(aci, masterKey, pin);
+        accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci);
 
         ManagerImpl m = null;
         try {
-            m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
+            m = new ManagerImpl(account, pathConfig, accountFileUpdater, serviceEnvironmentConfig, userAgent);
             account = null;
 
             m.refreshPreKeys();
diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
new file mode 100644 (file)
index 0000000..f1d37e8
--- /dev/null
@@ -0,0 +1,152 @@
+package org.asamk.signal.manager;
+
+import org.asamk.signal.manager.api.AccountCheckException;
+import org.asamk.signal.manager.api.NotRegisteredException;
+import org.asamk.signal.manager.config.ServiceConfig;
+import org.asamk.signal.manager.config.ServiceEnvironment;
+import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
+import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.accounts.AccountsStore;
+import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
+import org.asamk.signal.manager.util.KeyUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.libsignal.util.KeyHelper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class SignalAccountFiles {
+
+    private static final Logger logger = LoggerFactory.getLogger(MultiAccountManager.class);
+
+    private final PathConfig pathConfig;
+    private final ServiceEnvironmentConfig serviceEnvironmentConfig;
+    private final String userAgent;
+    private final TrustNewIdentity trustNewIdentity;
+    private final AccountsStore accountsStore;
+
+    public SignalAccountFiles(
+            final File settingsPath,
+            final ServiceEnvironment serviceEnvironment,
+            final String userAgent,
+            final TrustNewIdentity trustNewIdentity
+    ) {
+        this.pathConfig = PathConfig.createDefault(settingsPath);
+        this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
+        this.userAgent = userAgent;
+        this.trustNewIdentity = trustNewIdentity;
+        this.accountsStore = new AccountsStore(pathConfig.dataPath());
+    }
+
+    public Set<String> getAllLocalAccountNumbers() {
+        return accountsStore.getAllNumbers();
+    }
+
+    public MultiAccountManager initMultiAccountManager() {
+        final var managers = getAllLocalAccountNumbers().stream().map(a -> {
+            try {
+                return initManager(a);
+            } catch (NotRegisteredException | IOException | AccountCheckException e) {
+                logger.warn("Ignoring {}: {} ({})", a, e.getMessage(), e.getClass().getSimpleName());
+                return null;
+            }
+        }).filter(Objects::nonNull).toList();
+
+        return new MultiAccountManagerImpl(managers, this);
+    }
+
+    public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
+        final var accountPath = accountsStore.getPathByNumber(number);
+        if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
+            throw new NotRegisteredException();
+        }
+
+        var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
+        if (!number.equals(account.getNumber())) {
+            account.close();
+            throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
+        }
+
+        if (!account.isRegistered()) {
+            account.close();
+            throw new NotRegisteredException();
+        }
+
+        account.initDatabase();
+
+        final var manager = new ManagerImpl(account,
+                pathConfig,
+                (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
+                serviceEnvironmentConfig,
+                userAgent);
+
+        try {
+            manager.checkAccountState();
+        } catch (IOException e) {
+            manager.close();
+            throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
+        }
+
+        return manager;
+    }
+
+    public ProvisioningManager initProvisioningManager() {
+        return initProvisioningManager(null);
+    }
+
+    public ProvisioningManager initProvisioningManager(Consumer<Manager> newManagerListener) {
+        return new ProvisioningManagerImpl(pathConfig,
+                serviceEnvironmentConfig,
+                userAgent,
+                newManagerListener,
+                accountsStore);
+    }
+
+    public RegistrationManager initRegistrationManager(String number) throws IOException {
+        return initRegistrationManager(number, null);
+    }
+
+    public RegistrationManager initRegistrationManager(
+            String number, Consumer<Manager> newManagerListener
+    ) throws IOException {
+        final var accountPath = accountsStore.getPathByNumber(number);
+        if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
+            final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath;
+            var identityKey = KeyUtils.generateIdentityKeyPair();
+            var registrationId = KeyHelper.generateRegistrationId(false);
+
+            var profileKey = KeyUtils.createProfileKey();
+            var account = SignalAccount.create(pathConfig.dataPath(),
+                    newAccountPath,
+                    number,
+                    identityKey,
+                    registrationId,
+                    profileKey,
+                    trustNewIdentity);
+
+            return new RegistrationManagerImpl(account,
+                    pathConfig,
+                    serviceEnvironmentConfig,
+                    userAgent,
+                    newManagerListener,
+                    (newNumber, newAci) -> accountsStore.updateAccount(newAccountPath, newNumber, newAci));
+        }
+
+        var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
+        if (!number.equals(account.getNumber())) {
+            account.close();
+            throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
+        }
+
+        return new RegistrationManagerImpl(account,
+                pathConfig,
+                serviceEnvironmentConfig,
+                userAgent,
+                newManagerListener,
+                (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci));
+    }
+}
diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java
new file mode 100644 (file)
index 0000000..fb6e327
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.manager.helper;
+
+import org.whispersystems.signalservice.api.push.ACI;
+
+public interface AccountFileUpdater {
+
+    void updateAccountIdentifiers(String number, ACI aci);
+}
index 2b90a4fc655167d95b091ac8cca1a22ca802c256..3e60ce74281ba8e5b9d91aaf1743ddf446340dae 100644 (file)
@@ -52,7 +52,9 @@ public class AccountHelper {
         try {
             context.getPreKeyHelper().refreshPreKeysIfNecessary();
             if (account.getAci() == null) {
-                account.setAci(ACI.parseOrNull(dependencies.getAccountManager().getWhoAmI().getAci()));
+                final var aci = ACI.parseOrNull(dependencies.getAccountManager().getWhoAmI().getAci());
+                account.setAci(aci);
+                context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), aci);
             }
             updateAccountAttributes();
         } catch (AuthorizationFailedException e) {
index 11de336ed67150b9f87f7beeb8944b8245395781..522143ee71182fed03425a1263e53ea45d49c7b3 100644 (file)
@@ -4,8 +4,8 @@ import org.asamk.signal.manager.AttachmentStore;
 import org.asamk.signal.manager.AvatarStore;
 import org.asamk.signal.manager.JobExecutor;
 import org.asamk.signal.manager.SignalDependencies;
-import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore;
 import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore;
 
 import java.util.function.Supplier;
 
@@ -14,6 +14,7 @@ public class Context {
     private final Object LOCK = new Object();
 
     private final SignalAccount account;
+    private final AccountFileUpdater accountFileUpdater;
     private final SignalDependencies dependencies;
     private final AvatarStore avatarStore;
     private final StickerPackStore stickerPackStore;
@@ -40,12 +41,14 @@ public class Context {
 
     public Context(
             final SignalAccount account,
+            final AccountFileUpdater accountFileUpdater,
             final SignalDependencies dependencies,
             final AvatarStore avatarStore,
             final AttachmentStore attachmentStore,
             final StickerPackStore stickerPackStore
     ) {
         this.account = account;
+        this.accountFileUpdater = accountFileUpdater;
         this.dependencies = dependencies;
         this.avatarStore = avatarStore;
         this.stickerPackStore = stickerPackStore;
@@ -57,6 +60,10 @@ public class Context {
         return account;
     }
 
+    public AccountFileUpdater getAccountFileUpdater() {
+        return accountFileUpdater;
+    }
+
     public SignalDependencies getDependencies() {
         return dependencies;
     }
index dfed3d43a005aa3188ef02511a82aa5953434c75..6f136b3c6ec24839862b1a7587d7e91946fe7410 100644 (file)
@@ -1,8 +1,8 @@
 package org.asamk.signal.manager.helper;
 
 import org.asamk.signal.manager.SignalDependencies;
-import org.asamk.signal.manager.api.TrustLevel;
 import org.asamk.signal.manager.api.PhoneNumberSharingMode;
+import org.asamk.signal.manager.api.TrustLevel;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.storage.recipients.Contact;
@@ -188,7 +188,7 @@ public class StorageHelper {
             return;
         }
 
-        if (!accountRecord.getE164().equals(account.getAccount())) {
+        if (!accountRecord.getE164().equals(account.getNumber())) {
             // TODO implement changed number handling
         }
 
index d026b25a640c977f2801e91d8af11760cc1fbd87..d77d51a2ad502a4360c5a4a9e31c310b8067ba30 100644 (file)
@@ -3,8 +3,8 @@ package org.asamk.signal.manager.storage;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-import org.asamk.signal.manager.api.TrustLevel;
 import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.api.TrustLevel;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
 import org.asamk.signal.manager.storage.contacts.ContactsStore;
@@ -87,7 +87,8 @@ public class SignalAccount implements Closeable {
     private int previousStorageVersion;
 
     private File dataPath;
-    private String account;
+    private String accountPath;
+    private String number;
     private ACI aci;
     private String encryptedDeviceName;
     private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
@@ -132,23 +133,18 @@ public class SignalAccount implements Closeable {
     }
 
     public static SignalAccount load(
-            File dataPath, String account, boolean waitForLock, final TrustNewIdentity trustNewIdentity
+            File dataPath, String accountPath, boolean waitForLock, final TrustNewIdentity trustNewIdentity
     ) throws IOException {
         logger.trace("Opening account file");
-        final var fileName = getFileName(dataPath, account);
+        final var fileName = getFileName(dataPath, accountPath);
         final var pair = openFileChannel(fileName, waitForLock);
         try {
             var signalAccount = new SignalAccount(pair.first(), pair.second());
             logger.trace("Loading account file");
-            signalAccount.load(dataPath, trustNewIdentity);
+            signalAccount.load(dataPath, accountPath, trustNewIdentity);
             logger.trace("Migrating legacy parts of account file");
             signalAccount.migrateLegacyConfigs();
 
-            if (!account.equals(signalAccount.getAccount())) {
-                throw new IOException("Number in account file doesn't match expected number: "
-                        + signalAccount.getAccount());
-            }
-
             return signalAccount;
         } catch (Throwable e) {
             pair.second().close();
@@ -159,14 +155,15 @@ public class SignalAccount implements Closeable {
 
     public static SignalAccount create(
             File dataPath,
-            String account,
+            String accountPath,
+            String number,
             IdentityKeyPair identityKey,
             int registrationId,
             ProfileKey profileKey,
             final TrustNewIdentity trustNewIdentity
     ) throws IOException {
         IOUtils.createPrivateDirectories(dataPath);
-        var fileName = getFileName(dataPath, account);
+        var fileName = getFileName(dataPath, accountPath);
         if (!fileName.exists()) {
             IOUtils.createPrivateFile(fileName);
         }
@@ -174,14 +171,15 @@ public class SignalAccount implements Closeable {
         final var pair = openFileChannel(fileName, true);
         var signalAccount = new SignalAccount(pair.first(), pair.second());
 
-        signalAccount.account = account;
+        signalAccount.accountPath = accountPath;
+        signalAccount.number = number;
         signalAccount.profileKey = profileKey;
 
         signalAccount.dataPath = dataPath;
         signalAccount.identityKeyPair = identityKey;
         signalAccount.localRegistrationId = registrationId;
         signalAccount.trustNewIdentity = trustNewIdentity;
-        signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
+        signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
                 signalAccount.getRecipientStore(),
                 signalAccount::saveGroupStore);
         signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
@@ -198,7 +196,8 @@ public class SignalAccount implements Closeable {
 
     public static SignalAccount createOrUpdateLinkedAccount(
             File dataPath,
-            String account,
+            String accountPath,
+            String number,
             ACI aci,
             String password,
             String encryptedDeviceName,
@@ -209,10 +208,11 @@ public class SignalAccount implements Closeable {
             final TrustNewIdentity trustNewIdentity
     ) throws IOException {
         IOUtils.createPrivateDirectories(dataPath);
-        var fileName = getFileName(dataPath, account);
+        var fileName = getFileName(dataPath, accountPath);
         if (!fileName.exists()) {
             return createLinkedAccount(dataPath,
-                    account,
+                    accountPath,
+                    number,
                     aci,
                     password,
                     encryptedDeviceName,
@@ -223,8 +223,8 @@ public class SignalAccount implements Closeable {
                     trustNewIdentity);
         }
 
-        final var signalAccount = load(dataPath, account, true, trustNewIdentity);
-        signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
+        final var signalAccount = load(dataPath, accountPath, true, trustNewIdentity);
+        signalAccount.setProvisioningData(number, aci, password, encryptedDeviceName, deviceId, profileKey);
         signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress());
         signalAccount.getSessionStore().archiveAllSessions();
         signalAccount.getSenderKeyStore().deleteAll();
@@ -246,7 +246,8 @@ public class SignalAccount implements Closeable {
 
     private static SignalAccount createLinkedAccount(
             File dataPath,
-            String account,
+            String accountPath,
+            String number,
             ACI aci,
             String password,
             String encryptedDeviceName,
@@ -256,19 +257,20 @@ public class SignalAccount implements Closeable {
             ProfileKey profileKey,
             final TrustNewIdentity trustNewIdentity
     ) throws IOException {
-        var fileName = getFileName(dataPath, account);
+        var fileName = getFileName(dataPath, accountPath);
         IOUtils.createPrivateFile(fileName);
 
         final var pair = openFileChannel(fileName, true);
         var signalAccount = new SignalAccount(pair.first(), pair.second());
 
-        signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
+        signalAccount.setProvisioningData(number, aci, password, encryptedDeviceName, deviceId, profileKey);
 
         signalAccount.dataPath = dataPath;
+        signalAccount.accountPath = accountPath;
         signalAccount.identityKeyPair = identityKey;
         signalAccount.localRegistrationId = registrationId;
         signalAccount.trustNewIdentity = trustNewIdentity;
-        signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
+        signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
                 signalAccount.getRecipientStore(),
                 signalAccount::saveGroupStore);
         signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
@@ -283,14 +285,14 @@ public class SignalAccount implements Closeable {
     }
 
     private void setProvisioningData(
-            final String account,
+            final String number,
             final ACI aci,
             final String password,
             final String encryptedDeviceName,
             final int deviceId,
             final ProfileKey profileKey
     ) {
-        this.account = account;
+        this.number = number;
         this.aci = aci;
         this.password = password;
         this.profileKey = profileKey;
@@ -396,7 +398,7 @@ public class SignalAccount implements Closeable {
         return new File(getUserPath(dataPath, account), "account.db");
     }
 
-    public static boolean userExists(File dataPath, String account) {
+    public static boolean accountFileExists(File dataPath, String account) {
         if (account == null) {
             return false;
         }
@@ -405,9 +407,11 @@ public class SignalAccount implements Closeable {
     }
 
     private void load(
-            File dataPath, final TrustNewIdentity trustNewIdentity
+            File dataPath, String accountPath, final TrustNewIdentity trustNewIdentity
     ) throws IOException {
-        JsonNode rootNode;
+        this.dataPath = dataPath;
+        this.accountPath = accountPath;
+        final JsonNode rootNode;
         synchronized (fileChannel) {
             fileChannel.position(0);
             rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
@@ -423,7 +427,7 @@ public class SignalAccount implements Closeable {
             previousStorageVersion = accountVersion;
         }
 
-        account = Utils.getNotNullNode(rootNode, "username").asText();
+        number = Utils.getNotNullNode(rootNode, "username").asText();
         if (rootNode.hasNonNull("password")) {
             password = rootNode.get("password").asText();
         }
@@ -501,7 +505,6 @@ public class SignalAccount implements Closeable {
             migratedLegacyConfig = true;
         }
 
-        this.dataPath = dataPath;
         this.identityKeyPair = identityKeyPair;
         this.localRegistrationId = registrationId;
         this.trustNewIdentity = trustNewIdentity;
@@ -511,11 +514,11 @@ public class SignalAccount implements Closeable {
         if (rootNode.hasNonNull("groupStore")) {
             groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
             groupStore = GroupStore.fromStorage(groupStoreStorage,
-                    getGroupCachePath(dataPath, account),
+                    getGroupCachePath(dataPath, accountPath),
                     getRecipientStore(),
                     this::saveGroupStore);
         } else {
-            groupStore = new GroupStore(getGroupCachePath(dataPath, account),
+            groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
                     getRecipientStore(),
                     this::saveGroupStore);
         }
@@ -732,7 +735,7 @@ public class SignalAccount implements Closeable {
         synchronized (fileChannel) {
             var rootNode = jsonProcessor.createObjectNode();
             rootNode.put("version", CURRENT_STORAGE_VERSION)
-                    .put("username", account)
+                    .put("username", number)
                     .put("uuid", aci == null ? null : aci.toString())
                     .put("deviceName", encryptedDeviceName)
                     .put("deviceId", deviceId)
@@ -821,22 +824,23 @@ public class SignalAccount implements Closeable {
     }
 
     private PreKeyStore getPreKeyStore() {
-        return getOrCreate(() -> preKeyStore, () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account)));
+        return getOrCreate(() -> preKeyStore,
+                () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, accountPath)));
     }
 
     private SignedPreKeyStore getSignedPreKeyStore() {
         return getOrCreate(() -> signedPreKeyStore,
-                () -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account)));
+                () -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, accountPath)));
     }
 
     public SessionStore getSessionStore() {
         return getOrCreate(() -> sessionStore,
-                () -> sessionStore = new SessionStore(getSessionsPath(dataPath, account), getRecipientStore()));
+                () -> sessionStore = new SessionStore(getSessionsPath(dataPath, accountPath), getRecipientStore()));
     }
 
     public IdentityKeyStore getIdentityKeyStore() {
         return getOrCreate(() -> identityKeyStore,
-                () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account),
+                () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath),
                         getRecipientStore(),
                         identityKeyPair,
                         localRegistrationId,
@@ -853,7 +857,7 @@ public class SignalAccount implements Closeable {
 
     public RecipientStore getRecipientStore() {
         return getOrCreate(() -> recipientStore,
-                () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account),
+                () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, accountPath),
                         this::mergeRecipients));
     }
 
@@ -867,8 +871,8 @@ public class SignalAccount implements Closeable {
 
     public SenderKeyStore getSenderKeyStore() {
         return getOrCreate(() -> senderKeyStore,
-                () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account),
-                        getSenderKeysPath(dataPath, account),
+                () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, accountPath),
+                        getSenderKeysPath(dataPath, accountPath),
                         getRecipientStore()::resolveRecipientAddress,
                         getRecipientStore()));
     }
@@ -879,13 +883,13 @@ public class SignalAccount implements Closeable {
 
     public MessageCache getMessageCache() {
         return getOrCreate(() -> messageCache,
-                () -> messageCache = new MessageCache(getMessageCachePath(dataPath, account)));
+                () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
     }
 
     public AccountDatabase getAccountDatabase() {
         return getOrCreate(() -> accountDatabase, () -> {
             try {
-                accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, account));
+                accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
             } catch (SQLException e) {
                 throw new RuntimeException(e);
             }
@@ -897,8 +901,8 @@ public class SignalAccount implements Closeable {
                 () -> messageSendLogStore = new MessageSendLogStore(getRecipientStore(), getAccountDatabase()));
     }
 
-    public String getAccount() {
-        return account;
+    public String getNumber() {
+        return number;
     }
 
     public ACI getAci() {
@@ -911,11 +915,11 @@ public class SignalAccount implements Closeable {
     }
 
     public SignalServiceAddress getSelfAddress() {
-        return new SignalServiceAddress(aci, account);
+        return new SignalServiceAddress(aci, number);
     }
 
     public RecipientAddress getSelfRecipientAddress() {
-        return new RecipientAddress(aci == null ? null : aci.uuid(), account);
+        return new RecipientAddress(aci == null ? null : aci.uuid(), number);
     }
 
     public RecipientId getSelfRecipientId() {
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
new file mode 100644 (file)
index 0000000..ed6dec9
--- /dev/null
@@ -0,0 +1,52 @@
+package org.asamk.signal.manager.storage.accounts;
+
+import org.whispersystems.signalservice.api.push.ACI;
+import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class AccountsStore {
+
+    private final File dataPath;
+
+    public AccountsStore(final File dataPath) {
+        this.dataPath = dataPath;
+    }
+
+    public Set<String> getAllNumbers() {
+        final var files = dataPath.listFiles();
+
+        if (files == null) {
+            return Set.of();
+        }
+
+        return Arrays.stream(files)
+                .filter(File::isFile)
+                .map(File::getName)
+                .filter(file -> PhoneNumberFormatter.isValidNumber(file, null))
+                .collect(Collectors.toSet());
+    }
+
+    public String getPathByNumber(String number) {
+        return number;
+    }
+
+    public String getPathByAci(ACI aci) {
+        return null;
+    }
+
+    public void updateAccount(String path, String number, ACI aci) {
+        // TODO remove number and uuid from all other accounts
+        if (!path.equals(number)) {
+            throw new UnsupportedOperationException("Updating number not supported yet");
+        }
+    }
+
+    public String addAccount(String number, ACI aci) {
+        // TODO remove number and uuid from all other accounts
+        return number;
+    }
+}
index 4e5b1e9fcb9be3d3d7a61f3b4132133a099f12c0..5853e4b8322bec2773d73ad2196e909d805aea1d 100644 (file)
@@ -22,9 +22,8 @@ import org.asamk.signal.dbus.DbusMultiAccountManagerImpl;
 import org.asamk.signal.dbus.DbusProvisioningManagerImpl;
 import org.asamk.signal.dbus.DbusRegistrationManagerImpl;
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.MultiAccountManager;
-import org.asamk.signal.manager.ProvisioningManager;
 import org.asamk.signal.manager.RegistrationManager;
+import org.asamk.signal.manager.SignalAccountFiles;
 import org.asamk.signal.manager.api.AccountCheckException;
 import org.asamk.signal.manager.api.NotRegisteredException;
 import org.asamk.signal.manager.config.ServiceConfig;
@@ -164,26 +163,27 @@ public class App {
                 ? TrustNewIdentity.ON_FIRST_USE
                 : trustNewIdentityCli == TrustNewIdentityCli.ALWAYS ? TrustNewIdentity.ALWAYS : TrustNewIdentity.NEVER;
 
+        final SignalAccountFiles signalAccountFiles = new SignalAccountFiles(configPath,
+                serviceEnvironment,
+                BaseConfig.USER_AGENT,
+                trustNewIdentity);
+
         if (command instanceof ProvisioningCommand provisioningCommand) {
             if (account != null) {
                 throw new UserErrorException("You cannot specify a account (phone number) when linking");
             }
 
-            handleProvisioningCommand(provisioningCommand, configPath, serviceEnvironment, outputWriter);
+            handleProvisioningCommand(provisioningCommand, signalAccountFiles, outputWriter);
             return;
         }
 
         if (account == null) {
             if (command instanceof MultiLocalCommand multiLocalCommand) {
-                handleMultiLocalCommand(multiLocalCommand,
-                        configPath,
-                        serviceEnvironment,
-                        outputWriter,
-                        trustNewIdentity);
+                handleMultiLocalCommand(multiLocalCommand, signalAccountFiles, outputWriter);
                 return;
             }
 
-            var accounts = MultiAccountManager.getAllLocalAccountNumbers(configPath);
+            var accounts = signalAccountFiles.getAllLocalAccountNumbers();
             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) {
@@ -191,13 +191,13 @@ public class App {
                         "Multiple users found, you need to specify an account (phone number) with -a");
             }
 
-            account = accounts.get(0);
+            account = accounts.stream().findFirst().get();
         } else if (!Manager.isValidNumber(account, null)) {
             throw new UserErrorException("Invalid account (phone number), make sure you include the country code.");
         }
 
         if (command instanceof RegistrationCommand registrationCommand) {
-            handleRegistrationCommand(registrationCommand, account, configPath, serviceEnvironment);
+            handleRegistrationCommand(registrationCommand, account, signalAccountFiles);
             return;
         }
 
@@ -205,21 +205,15 @@ public class App {
             throw new UserErrorException("Command only works in multi-account mode");
         }
 
-        handleLocalCommand((LocalCommand) command,
-                account,
-                configPath,
-                serviceEnvironment,
-                outputWriter,
-                trustNewIdentity);
+        handleLocalCommand((LocalCommand) command, account, signalAccountFiles, outputWriter);
     }
 
     private void handleProvisioningCommand(
             final ProvisioningCommand command,
-            final File configPath,
-            final ServiceEnvironment serviceEnvironment,
+            final SignalAccountFiles signalAccountFiles,
             final OutputWriter outputWriter
     ) throws CommandException {
-        var pm = ProvisioningManager.init(configPath, serviceEnvironment, BaseConfig.USER_AGENT);
+        var pm = signalAccountFiles.initProvisioningManager();
         command.handleCommand(ns, pm, outputWriter);
     }
 
@@ -240,22 +234,9 @@ public class App {
     }
 
     private void handleRegistrationCommand(
-            final RegistrationCommand command,
-            final String account,
-            final File configPath,
-            final ServiceEnvironment serviceEnvironment
+            final RegistrationCommand command, final String account, final SignalAccountFiles signalAccountFiles
     ) throws CommandException {
-        final RegistrationManager manager;
-        try {
-            manager = RegistrationManager.init(account, configPath, serviceEnvironment, BaseConfig.USER_AGENT);
-        } catch (Throwable e) {
-            throw new UnexpectedErrorException("Error loading or creating state file: "
-                    + e.getMessage()
-                    + " ("
-                    + e.getClass().getSimpleName()
-                    + ")", e);
-        }
-        try (manager) {
+        try (final var manager = loadRegistrationManager(account, signalAccountFiles)) {
             command.handleCommand(ns, manager);
         } catch (IOException e) {
             logger.warn("Cleanup failed", e);
@@ -280,12 +261,10 @@ public class App {
     private void handleLocalCommand(
             final LocalCommand command,
             final String account,
-            final File configPath,
-            final ServiceEnvironment serviceEnvironment,
-            final OutputWriter outputWriter,
-            final TrustNewIdentity trustNewIdentity
+            final SignalAccountFiles signalAccountFiles,
+            final OutputWriter outputWriter
     ) throws CommandException {
-        try (var m = loadManager(account, configPath, serviceEnvironment, trustNewIdentity)) {
+        try (var m = loadManager(account, signalAccountFiles)) {
             command.handleCommand(ns, m, outputWriter);
         } catch (IOException e) {
             logger.warn("Cleanup failed", e);
@@ -310,15 +289,10 @@ public class App {
 
     private void handleMultiLocalCommand(
             final MultiLocalCommand command,
-            final File configPath,
-            final ServiceEnvironment serviceEnvironment,
-            final OutputWriter outputWriter,
-            final TrustNewIdentity trustNewIdentity
+            final SignalAccountFiles signalAccountFiles,
+            final OutputWriter outputWriter
     ) throws CommandException {
-        try (var multiAccountManager = MultiAccountManager.init(configPath,
-                serviceEnvironment,
-                BaseConfig.USER_AGENT,
-                trustNewIdentity)) {
+        try (var multiAccountManager = signalAccountFiles.initMultiAccountManager()) {
             command.handleCommand(ns, multiAccountManager, outputWriter);
         }
     }
@@ -336,15 +310,26 @@ public class App {
         }
     }
 
+    private RegistrationManager loadRegistrationManager(
+            final String account, final SignalAccountFiles signalAccountFiles
+    ) throws UnexpectedErrorException {
+        try {
+            return signalAccountFiles.initRegistrationManager(account);
+        } catch (Throwable e) {
+            throw new UnexpectedErrorException("Error loading or creating state file: "
+                    + e.getMessage()
+                    + " ("
+                    + e.getClass().getSimpleName()
+                    + ")", e);
+        }
+    }
+
     private Manager loadManager(
-            final String account,
-            final File configPath,
-            final ServiceEnvironment serviceEnvironment,
-            final TrustNewIdentity trustNewIdentity
+            final String account, final SignalAccountFiles signalAccountFiles
     ) throws CommandException {
         logger.trace("Loading account file for {}", account);
         try {
-            return Manager.init(account, configPath, serviceEnvironment, BaseConfig.USER_AGENT, trustNewIdentity);
+            return signalAccountFiles.initManager(account);
         } catch (NotRegisteredException e) {
             throw new UserErrorException("User " + account + " is not registered.");
         } catch (AccountCheckException ace) {
index f0b785905f3cfd27e2cc7fce90d47a9c97868709..14bdc4c92ded097bb296eeaf4e1b493fd0971f49 100644 (file)
@@ -27,7 +27,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
 import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
 import org.asamk.signal.commands.exceptions.UserErrorException;
-import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.ManagerLogger;
 import org.asamk.signal.util.SecurityProvider;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.slf4j.bridge.SLF4JBridgeHandler;
@@ -97,7 +97,7 @@ public class Main {
         if (verboseLevel > 0) {
             java.util.logging.Logger.getLogger("")
                     .setLevel(verboseLevel > 2 ? java.util.logging.Level.FINEST : java.util.logging.Level.INFO);
-            Manager.initLogger();
+            ManagerLogger.initLogger();
         }
         SLF4JBridgeHandler.removeHandlersForRootLogger();
         SLF4JBridgeHandler.install();