+ return readAccounts().stream()
+ .map(AccountsStorage.Account::number)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ }
+
+ public String getPathByNumber(String number) {
+ return readAccounts().stream()
+ .filter(a -> number.equals(a.number()))
+ .map(AccountsStorage.Account::path)
+ .findFirst()
+ .orElse(null);
+ }
+
+ public String getPathByAci(ACI aci) {
+ return readAccounts().stream()
+ .filter(a -> aci.toString().equals(a.uuid()))
+ .map(AccountsStorage.Account::path)
+ .findFirst()
+ .orElse(null);
+ }
+
+ public void updateAccount(String path, String number, ACI aci) {
+ updateAccounts(accounts -> accounts.stream().map(a -> {
+ if (path.equals(a.path())) {
+ return new AccountsStorage.Account(a.path(), number, aci == null ? null : aci.toString());
+ }
+
+ if (number != null && number.equals(a.number())) {
+ return new AccountsStorage.Account(a.path(), null, a.uuid());
+ }
+ if (aci != null && aci.toString().equals(a.toString())) {
+ return new AccountsStorage.Account(a.path(), a.number(), null);
+ }
+
+ return a;
+ }).toList());
+ }
+
+ public String addAccount(String number, ACI aci) {
+ final var accountPath = generateNewAccountPath();
+ final var account = new AccountsStorage.Account(accountPath, number, aci == null ? null : aci.toString());
+ updateAccounts(accounts -> {
+ final var existingAccounts = accounts.stream().map(a -> {
+ if (number != null && number.equals(a.number())) {
+ return new AccountsStorage.Account(a.path(), null, a.uuid());
+ }
+ if (aci != null && aci.toString().equals(a.toString())) {
+ return new AccountsStorage.Account(a.path(), a.number(), null);
+ }
+
+ return a;
+ });
+ return Stream.concat(existingAccounts, Stream.of(account)).toList();
+ });
+ return accountPath;
+ }
+
+ private String generateNewAccountPath() {
+ return new Random().ints(100000, 1000000)
+ .mapToObj(String::valueOf)
+ .filter(n -> !new File(dataPath, n).exists() && !new File(dataPath, n + ".d").exists())
+ .findFirst()
+ .get();
+ }
+
+ private File getAccountsFile() {
+ return new File(dataPath, "accounts.json");
+ }
+
+ 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());
+
+ IOUtils.createPrivateDirectories(dataPath);
+ var fileName = getAccountsFile();
+ if (!fileName.exists()) {
+ IOUtils.createPrivateFile(fileName);
+ }
+
+ final var pair = openFileChannel(getAccountsFile());
+ try (final var fileChannel = pair.first(); final var lock = pair.second()) {
+ saveAccountsLocked(fileChannel, accountsStorage);
+ }
+ }
+
+ private Set<String> getLegacyAccountPaths() {