diff --git a/app/src/main/java/foundation/e/notes/migration/Migration_24_25.kt b/app/src/main/java/foundation/e/notes/migration/Migration_24_25.kt new file mode 100644 index 0000000000000000000000000000000000000000..86b40c6c458b58df6793cbbd64007045464056d0 --- /dev/null +++ b/app/src/main/java/foundation/e/notes/migration/Migration_24_25.kt @@ -0,0 +1,33 @@ +/* + * Copyright e Foundation 2025 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.notes.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +/** + * Deletes accounts without any associated notes. + * */ +class Migration_24_25 : Migration(24, 25) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + DELETE FROM Account WHERE id NOT IN (SELECT DISTINCT accountId FROM Note); + """.trimIndent() + ) + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java index 0ab3fd9bf5c4da89dc08d5fe52dfee0cdb9f0c38..a25143c87608e1fa6449b0243c63d5f4f1fff065 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountActivity.java @@ -19,6 +19,7 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedExcepti import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException; import com.nextcloud.android.sso.exceptions.UnknownErrorException; import com.nextcloud.android.sso.helper.SingleAccountHelper; +import com.nextcloud.android.sso.model.SingleSignOnAccount; import com.nextcloud.android.sso.ui.UiExceptionManager; import java.net.HttpURLConnection; @@ -120,8 +121,13 @@ public class ImportAccountActivity extends AppCompatActivity { AccountImporter.onActivityResult(requestCode, resultCode, data, ImportAccountActivity.this, ssoAccount -> { runOnUiThread(() -> binding.progressCircular.setVisibility(View.VISIBLE)); - SingleAccountHelper.setCurrentAccount(getApplicationContext(), ssoAccount.name); executor.submit(() -> { + if (isExistingAccount(ssoAccount)) { + finishActivity(false); + return; + } + + SingleAccountHelper.setCurrentAccount(getApplicationContext(), ssoAccount.name); Log.i(TAG, "Added account: " + "name:" + ssoAccount.name + ", " + ssoAccount.url + ", userId" + ssoAccount.userId); try { Log.i(TAG, "Loading capabilities for " + ssoAccount.name); @@ -192,6 +198,10 @@ public class ImportAccountActivity extends AppCompatActivity { } } + private boolean isExistingAccount(SingleSignOnAccount ssoAccount) { + return importAccountViewModel.isExistingAccount(ssoAccount); + } + private void finishActivity(boolean isLocal) { setResult(RESULT_OK); Intent intent = new Intent(ImportAccountActivity.this, MainActivity.class); @@ -243,7 +253,7 @@ public class ImportAccountActivity extends AppCompatActivity { private void handleLocalAccount() { binding.addLocalButton.setOnClickListener(view -> { - localAccountViewModel.logInToLocalAccount(getAddLocalAccountCallback()); + localAccountViewModel.logInToLocalAccount(getApplicationContext(), getAddLocalAccountCallback()); }); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java index 1d60a0434ca61c42b2ce1881b7b078139f8451e8..4affddb0a517dac22f6d1d6faaa4e24f463f68d8 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java @@ -7,6 +7,8 @@ import androidx.annotation.Nullable; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; +import com.nextcloud.android.sso.model.SingleSignOnAccount; + import it.niedermann.owncloud.notes.persistence.NotesRepository; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.shared.model.Capabilities; @@ -26,4 +28,14 @@ public class ImportAccountViewModel extends AndroidViewModel { public LiveData addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities, @Nullable String displayName, @NonNull IResponseCallback callback) { return repo.addAccount(url, username, accountName, capabilities, displayName, callback); } + + protected boolean isExistingAccount(@NonNull SingleSignOnAccount ssoAccount) { + Account account = repo.getAccountByName(ssoAccount.name); + + if (account == null) { + return false; + } + + return account.getUrl().equals(ssoAccount.url); + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java index ccad6f2c67bb4fddacfb2dae5efd98657493eb02..187796ab7126065c31087260222788295e8341f2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java @@ -823,7 +823,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A @Override public void onAddLocalAccountButtonClick() { - localAccountViewModel.logInToLocalAccount(new IResponseCallback<>() { + localAccountViewModel.logInToLocalAccount(getApplicationContext(), new IResponseCallback<>() { @Override public void onSuccess(@NonNull LocalAccountBundle result) { runOnUiThread(() -> { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java index 3072aa1bd3dd5defa80528711307bc3ab325de24..e9f2128a24dc16cb08426bb80eae061234250c05 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java @@ -12,6 +12,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase; import java.util.List; import foundation.e.notes.migration.Migration_23_24; +import foundation.e.notes.migration.Migration_24_25; import it.niedermann.owncloud.notes.persistence.dao.AccountDao; import it.niedermann.owncloud.notes.persistence.dao.CategoryOptionsDao; import it.niedermann.owncloud.notes.persistence.dao.NoteDao; @@ -46,7 +47,7 @@ import trikita.log.Log; CategoryOptions.class, SingleNoteWidgetData.class, NotesListWidgetData.class - }, version = 24 + }, version = 25 ) @TypeConverters({Converters.class}) public abstract class NotesDatabase extends RoomDatabase { @@ -82,7 +83,8 @@ public abstract class NotesDatabase extends RoomDatabase { new Migration_20_21(), new Migration_21_22(context), new Migration_22_23(), - new Migration_23_24() + new Migration_23_24(), + new Migration_24_25() ) .fallbackToDestructiveMigrationOnDowngrade() .fallbackToDestructiveMigration() diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/account/LocalAccountViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/account/LocalAccountViewModel.java index f6bf22ecec367331b37550fdba4c58a0e6d326a5..aab31d05fb882f90d90f96f844663ce72c2c37a3 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/account/LocalAccountViewModel.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/account/LocalAccountViewModel.java @@ -17,11 +17,13 @@ package it.niedermann.owncloud.notes.shared.account; import android.app.Application; +import android.content.Context; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; import com.nextcloud.android.sso.AccountImporter; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; import com.nextcloud.android.sso.model.SingleSignOnAccount; import java.util.concurrent.ExecutorService; @@ -36,8 +38,8 @@ import trikita.log.Log; public class LocalAccountViewModel extends AndroidViewModel { + private static final String ACCOUNT_NAME_LOCAL = "Local"; private static final String TAG = LocalAccountViewModel.class.getSimpleName(); - @NonNull private final NotesRepository notesRepository; @@ -50,31 +52,72 @@ public class LocalAccountViewModel extends AndroidViewModel { executor = Executors.newSingleThreadExecutor(); } - public void logInToLocalAccount(@NonNull IResponseCallback callback) { + private static boolean isExistingLocalAccount(@NonNull SingleSignOnAccount ssoAccount) { + return !ssoAccount.name.isEmpty() && !ssoAccount.userId.isEmpty() && !ssoAccount.type.isEmpty(); + } + + public void logInToLocalAccount(Context context, @NonNull IResponseCallback callback) { executor.submit(() -> { - SingleSignOnAccount singleSignOnAccount = AccountImporter.pickLocalAccount(getApplication()); - Log.i(TAG, "Added local account: " + "name:" + singleSignOnAccount.name); try { - final var capabilities = new Capabilities(); - final String displayName = singleSignOnAccount.name; - notesRepository.addAccount(singleSignOnAccount.url, singleSignOnAccount.userId, singleSignOnAccount.name, capabilities, displayName, new IResponseCallback<>() { - @Override - public void onSuccess(Account account) { - final var bundle = new LocalAccountBundle(singleSignOnAccount, account, capabilities); - callback.onSuccess(bundle); - } - - @Override - public void onError(@NonNull Throwable throwable) { - Log.e(TAG, throwable); - callback.onError(throwable); - } - }); - } catch (Throwable t) { - ApiProvider.getInstance().invalidateAPICache(singleSignOnAccount); - Log.e(TAG, t); - callback.onError(t); + Account account = getExistingAccount(); + if (account != null) { + handleExistingAccount(context, account, callback); + return; + } + } catch (NextcloudFilesAppAccountNotFoundException accountNotFoundException) { + Log.e(TAG, "Failed to get SingleSignOn account: " + accountNotFoundException); } + addNewAccount(callback); }); } + + private Account getExistingAccount() { + return notesRepository.getAccountByName(ACCOUNT_NAME_LOCAL); + } + + private void addNewAccount(@NonNull IResponseCallback callback) { + SingleSignOnAccount singleSignOnAccount = createSingleSignOnAccount(); + try { + final var capabilities = new Capabilities(); + final String displayName = singleSignOnAccount.name; + + notesRepository.addAccount(singleSignOnAccount.url, singleSignOnAccount.userId, + singleSignOnAccount.name, capabilities, displayName, new IResponseCallback<>() { + @Override + public void onSuccess(Account account) { + final var bundle = new LocalAccountBundle(singleSignOnAccount, account, capabilities); + Log.d(TAG, "onSuccess: added local account, name: " + singleSignOnAccount.name); + callback.onSuccess(bundle); + } + + @Override + public void onError(@NonNull Throwable throwable) { + Log.e(TAG, "onError: couldn't add local account, error: " + throwable); + callback.onError(throwable); + } + }); + } catch (Throwable throwable) { + ApiProvider.getInstance().invalidateAPICache(singleSignOnAccount); + Log.e(TAG, throwable); + callback.onError(throwable); + } + } + + private void handleExistingAccount(Context context, @NonNull Account account, IResponseCallback callback) + throws NextcloudFilesAppAccountNotFoundException { + final SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(context, ACCOUNT_NAME_LOCAL); + if (isExistingLocalAccount(ssoAccount)) { + Log.i(TAG, "Local account already exists, reusing it; account name: " + ssoAccount.name); + final Capabilities capabilities = new Capabilities(); + final LocalAccountBundle bundle = new LocalAccountBundle(ssoAccount, account, capabilities); + callback.onSuccess(bundle); + } + } + + @NonNull + private SingleSignOnAccount createSingleSignOnAccount() { + SingleSignOnAccount singleSignOnAccount = AccountImporter.pickLocalAccount(getApplication()); + Log.i(TAG, "New local account created; account name: " + singleSignOnAccount.name); + return singleSignOnAccount; + } }