From 609b99666fd19967dad1d6543f14a5561e1a2d91 Mon Sep 17 00:00:00 2001 From: Chris Narkiewicz Date: Sat, 16 Nov 2019 22:17:22 +0000 Subject: [PATCH] New user model New non-nullable user model abstracts away all complexities of Nextcloud user account and provides. - backported java.util.Optional - extended UserAccountManager with User getters - deprecated CurrentAccountProvider.getCurrentAccount() method - migrated connectivity service to new user model - migrated UploadsStorageManager to new user model - migrated OCFileListAdapter Signed-off-by: Chris Narkiewicz --- .../nextcloud/client/account/AnonymousUser.kt | 57 +++ .../account/CurrentAccountProvider.java | 18 +- .../client/account/RegisteredUser.kt | 46 +++ .../com/nextcloud/client/account/Server.kt | 30 ++ .../java/com/nextcloud/client/account/User.kt | 53 +++ .../client/account/UserAccountManager.java | 19 + .../account/UserAccountManagerImpl.java | 94 +++++ .../network/ConnectivityServiceImpl.java | 76 ++-- .../client/network/NetworkModule.java | 7 +- .../com/nextcloud/java/util/Optional.java | 366 ++++++++++++++++++ .../java/util/function/Predicate.java | 121 ++++++ .../com/nextcloud/java/util/package-info.java | 26 ++ .../datamodel/UploadsStorageManager.java | 127 +++--- .../android/ui/adapter/OCFileListAdapter.java | 55 +-- .../ui/fragment/OCFileListFragment.java | 4 +- .../client/network/ConnectivityServiceTest.kt | 53 ++- 16 files changed, 994 insertions(+), 158 deletions(-) create mode 100644 src/main/java/com/nextcloud/client/account/AnonymousUser.kt create mode 100644 src/main/java/com/nextcloud/client/account/RegisteredUser.kt create mode 100644 src/main/java/com/nextcloud/client/account/Server.kt create mode 100644 src/main/java/com/nextcloud/client/account/User.kt create mode 100644 src/main/java/com/nextcloud/java/util/Optional.java create mode 100644 src/main/java/com/nextcloud/java/util/function/Predicate.java create mode 100644 src/main/java/com/nextcloud/java/util/package-info.java diff --git a/src/main/java/com/nextcloud/client/account/AnonymousUser.kt b/src/main/java/com/nextcloud/client/account/AnonymousUser.kt new file mode 100644 index 0000000000..d949cf5bf3 --- /dev/null +++ b/src/main/java/com/nextcloud/client/account/AnonymousUser.kt @@ -0,0 +1,57 @@ +/* + * Nextcloud Android client application + * + * @author Chris Narkiewicz + * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.nextcloud.client.account + +import android.accounts.Account +import android.content.Context +import android.net.Uri +import com.owncloud.android.MainApp +import com.owncloud.android.R +import com.owncloud.android.lib.common.OwnCloudAccount +import com.owncloud.android.lib.common.OwnCloudBasicCredentials +import java.net.URI + +/** + * This object represents anonymous user, ie. user that did not log in the Nextcloud server. + * It serves as a semantically correct "empty value", allowing simplification of logic + * in various components requiring user data, such as DB queries. + */ +internal class AnonymousUser(private val accountType: String) : User { + + companion object { + @JvmStatic + fun fromContext(context: Context): AnonymousUser { + val type = context.getString(R.string.account_type) + return AnonymousUser(type) + } + } + + override val accountName: String = "anonymous" + override val server = Server(URI.create(""), MainApp.MINIMUM_SUPPORTED_SERVER_VERSION) + + override fun toPlatformAccount(): Account { + return Account(accountName, accountType) + } + + override fun toOwnCloudAccount(): OwnCloudAccount { + return OwnCloudAccount(Uri.EMPTY, OwnCloudBasicCredentials("", "")) + } +} diff --git a/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java b/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java index f37624933b..ab6484599a 100644 --- a/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java +++ b/src/main/java/com/nextcloud/client/account/CurrentAccountProvider.java @@ -2,19 +2,31 @@ package com.nextcloud.client.account; import android.accounts.Account; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** - * This interface provides access to currently selected user Account. + * This interface provides access to currently selected user. + * * @see UserAccountManager */ -@FunctionalInterface public interface CurrentAccountProvider { /** - * Get currently active account. + * Get currently active account. * * @return Currently selected {@link Account} or first valid {@link Account} registered in OS or null, if not available at all. */ + @Deprecated @Nullable Account getCurrentAccount(); + + /** + * Get currently active user profile. If there is no actice user, anonymous user is returned. + * + * @return User profile. Profile is never null. + */ + @NonNull + default User getUser() { + return new AnonymousUser("dummy"); + } } diff --git a/src/main/java/com/nextcloud/client/account/RegisteredUser.kt b/src/main/java/com/nextcloud/client/account/RegisteredUser.kt new file mode 100644 index 0000000000..7a8a5f6314 --- /dev/null +++ b/src/main/java/com/nextcloud/client/account/RegisteredUser.kt @@ -0,0 +1,46 @@ +/* + * Nextcloud Android client application + * + * @author Chris Narkiewicz + * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.nextcloud.client.account + +import android.accounts.Account +import com.owncloud.android.lib.common.OwnCloudAccount + +/** + * This class represents normal user logged into the Nextcloud server. + */ +internal class RegisteredUser( + private val account: Account, + private val ownCloudAccount: OwnCloudAccount, + override val server: Server +) : User { + + override val accountName: String get() { + return account.name + } + + override fun toPlatformAccount(): Account { + return account + } + + override fun toOwnCloudAccount(): OwnCloudAccount { + return ownCloudAccount + } +} diff --git a/src/main/java/com/nextcloud/client/account/Server.kt b/src/main/java/com/nextcloud/client/account/Server.kt new file mode 100644 index 0000000000..0a44dfb4ec --- /dev/null +++ b/src/main/java/com/nextcloud/client/account/Server.kt @@ -0,0 +1,30 @@ +/* + * Nextcloud Android client application + * + * @author Chris Narkiewicz + * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.nextcloud.client.account + +import com.owncloud.android.lib.resources.status.OwnCloudVersion +import java.net.URI + +/** + * This object provides all information necessary to interact + * with backend server. + */ +data class Server(val uri: URI, val version: OwnCloudVersion) diff --git a/src/main/java/com/nextcloud/client/account/User.kt b/src/main/java/com/nextcloud/client/account/User.kt new file mode 100644 index 0000000000..408797d079 --- /dev/null +++ b/src/main/java/com/nextcloud/client/account/User.kt @@ -0,0 +1,53 @@ +/* + * Nextcloud Android client application + * + * @author Chris Narkiewicz + * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.nextcloud.client.account + +import android.accounts.Account +import com.owncloud.android.lib.common.OwnCloudAccount + +interface User { + val accountName: String + val server: Server + + /** + * This is temporary helper method created to facilitate incremental refactoring. + * Code using legacy platform Account can be partially converted to instantiate User + * object and use account instance when required. + * + * This method calls will allow tracing code awaiting further refactoring. + * + * @return Account instance that is associated with this User object. + */ + @Deprecated("Temporary workaround") + fun toPlatformAccount(): Account + + /** + * This is temporary helper method created to facilitate incremental refactoring. + * Code using legacy ownCloud account can be partially converted to instantiate User + * object and use account instance when required. + * + * This method calls will allow tracing code awaiting further refactoring. + * + * @return OwnCloudAccount instance that is associated with this User object. + */ + @Deprecated("Temporary workaround") + fun toOwnCloudAccount(): OwnCloudAccount +} diff --git a/src/main/java/com/nextcloud/client/account/UserAccountManager.java b/src/main/java/com/nextcloud/client/account/UserAccountManager.java index 5584541c49..0b217073e2 100644 --- a/src/main/java/com/nextcloud/client/account/UserAccountManager.java +++ b/src/main/java/com/nextcloud/client/account/UserAccountManager.java @@ -21,10 +21,13 @@ package com.nextcloud.client.account; import android.accounts.Account; +import com.nextcloud.java.util.Optional; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import java.util.List; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -51,6 +54,22 @@ public interface UserAccountManager extends CurrentAccountProvider { @NonNull Account[] getAccounts(); + /** + * Get configured nextcloud user accounts + * @return List of users or empty list, if users are not registered. + */ + @NonNull + List getAllUsers(); + + /** + * Get user with a specific account name. + * + * @param accountName Account name of the requested user + * @return User or empty optional if user does not exist. + */ + @NonNull + Optional getUser(CharSequence accountName); + /** * Check if Nextcloud account is registered in {@link android.accounts.AccountManager} * diff --git a/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java b/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java index 575580f049..4ea8246872 100644 --- a/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java +++ b/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java @@ -27,6 +27,7 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.text.TextUtils; +import com.nextcloud.java.util.Optional; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; @@ -35,11 +36,18 @@ import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.UserInfo; +import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.status.OwnCloudVersion; import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation; +import org.jetbrains.annotations.NotNull; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + import javax.inject.Inject; import androidx.annotation.NonNull; @@ -80,6 +88,20 @@ public class UserAccountManagerImpl implements UserAccountManager { return accountManager.getAccountsByType(getAccountType()); } + @Override + @NonNull + public List getAllUsers() { + Account[] accounts = getAccounts(); + List users = new ArrayList<>(accounts.length); + for (Account account : accounts) { + User user = createUserFromAccount(account); + if (user != null) { + users.add(user); + } + } + return users; + } + @Override public boolean exists(Account account) { Account[] nextcloudAccounts = getAccounts(); @@ -103,6 +125,7 @@ public class UserAccountManagerImpl implements UserAccountManager { return false; } + @Override @Nullable public Account getCurrentAccount() { Account[] ocAccounts = getAccounts(); @@ -139,6 +162,76 @@ public class UserAccountManagerImpl implements UserAccountManager { return defaultAccount; } + /** + * Temporary solution to convert platform account to user instance. + * It takes null and returns null on error to ease error handling + * in legacy code. + * + * @param account Account instance + * @return User instance or null, if conversion failed + */ + @Nullable + private User createUserFromAccount(@Nullable Account account) { + if (account == null) { + return null; + } + + OwnCloudAccount ownCloudAccount = null; + try { + ownCloudAccount = new OwnCloudAccount(account, context); + } catch (AccountUtils.AccountNotFoundException ex) { + return null; + } + + /* + * Server version + */ + String serverVersionStr = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_VERSION); + OwnCloudVersion serverVersion; + if (serverVersionStr != null) { + serverVersion = new OwnCloudVersion(serverVersionStr); + } else { + serverVersion = MainApp.MINIMUM_SUPPORTED_SERVER_VERSION; + } + + /* + * Server address + */ + String serverAddressStr = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL); + if (serverAddressStr == null || serverAddressStr.isEmpty()) { + return AnonymousUser.fromContext(context); + } + URI serverUri = URI.create(serverAddressStr); // TODO: validate + + return new RegisteredUser( + account, + ownCloudAccount, + new Server(serverUri, serverVersion) + ); + } + + /** + * Get user. If user cannot be retrieved due to data error, anonymous user is returned instead. + * + * + * @return User instance + */ + @NotNull + @Override + public User getUser() { + Account account = getCurrentAccount(); + User user = createUserFromAccount(account); + return user != null ? user : AnonymousUser.fromContext(context); + } + + @Override + @NonNull + public Optional getUser(CharSequence accountName) { + Account account = getAccountByName(accountName.toString()); + User user = createUserFromAccount(account); + return Optional.of(user); + } + @Override @Nullable public OwnCloudAccount getCurrentOwnCloudAccount() { @@ -197,6 +290,7 @@ public class UserAccountManagerImpl implements UserAccountManager { return result; } + @Deprecated @Override @NonNull public OwnCloudVersion getServerVersion(Account account) { diff --git a/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java b/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java index 9060b7922a..14f2a54cc4 100644 --- a/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java +++ b/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java @@ -20,14 +20,13 @@ package com.nextcloud.client.network; -import android.accounts.Account; import android.net.ConnectivityManager; import android.net.NetworkInfo; import com.evernote.android.job.JobRequest; +import com.nextcloud.client.account.Server; import com.nextcloud.client.account.UserAccountManager; -import com.owncloud.android.lib.common.OwnCloudAccount; -import com.owncloud.android.lib.common.utils.Log_OC; +import com.nextcloud.client.logger.Logger; import com.owncloud.android.lib.resources.status.OwnCloudVersion; import org.apache.commons.httpclient.HttpClient; @@ -44,10 +43,11 @@ class ConnectivityServiceImpl implements ConnectivityService { private final static String TAG = ConnectivityServiceImpl.class.getName(); - private ConnectivityManager connectivityManager; - private UserAccountManager accountManager; - private ClientFactory clientFactory; - private GetRequestBuilder requestBuilder; + private final ConnectivityManager connectivityManager; + private final UserAccountManager accountManager; + private final ClientFactory clientFactory; + private final GetRequestBuilder requestBuilder; + private final Logger logger; static class GetRequestBuilder implements Function1 { @Override @@ -59,55 +59,55 @@ class ConnectivityServiceImpl implements ConnectivityService { ConnectivityServiceImpl(ConnectivityManager connectivityManager, UserAccountManager accountManager, ClientFactory clientFactory, - GetRequestBuilder requestBuilder) { + GetRequestBuilder requestBuilder, + Logger logger) { this.connectivityManager = connectivityManager; this.accountManager = accountManager; this.clientFactory = clientFactory; this.requestBuilder = requestBuilder; + this.logger = logger; } @Override public boolean isInternetWalled() { if (isOnlineWithWifi()) { try { - Account account = accountManager.getCurrentAccount(); - OwnCloudAccount ocAccount = accountManager.getCurrentOwnCloudAccount(); - if (account != null && ocAccount != null) { - OwnCloudVersion serverVersion = accountManager.getServerVersion(account); + Server server = accountManager.getUser().getServer(); + String baseServerAddress = server.getUri().toString(); + if (baseServerAddress.isEmpty()) { + return true; + } + String url; + if (server.getVersion().compareTo(OwnCloudVersion.nextcloud_13) > 0) { + url = baseServerAddress + "/index.php/204"; + } else { + url = baseServerAddress + "/status.php"; + } - String url; - if (serverVersion.compareTo(OwnCloudVersion.nextcloud_13) > 0) { - url = ocAccount.getBaseUri() + "/index.php/204"; - } else { - url = ocAccount.getBaseUri() + "/status.php"; - } + GetMethod get = requestBuilder.invoke(url); + HttpClient client = clientFactory.createPlainClient(); - GetMethod get = requestBuilder.invoke(url); - HttpClient client = clientFactory.createPlainClient(); + int status = client.executeMethod(get); - int status = client.executeMethod(get); - - if (serverVersion.compareTo(OwnCloudVersion.nextcloud_13) > 0) { - return !(status == HttpStatus.SC_NO_CONTENT && - (get.getResponseContentLength() == -1 || get.getResponseContentLength() == 0)); - } else { - if (status == HttpStatus.SC_OK) { - try { - // try parsing json to verify response - // check if json contains maintenance and it should be false - - String json = get.getResponseBodyAsString(); - return new JSONObject(json).getBoolean("maintenance"); - } catch (Exception e) { - return true; - } - } else { + if (server.getVersion().compareTo(OwnCloudVersion.nextcloud_13) > 0) { + return !(status == HttpStatus.SC_NO_CONTENT && + (get.getResponseContentLength() == -1 || get.getResponseContentLength() == 0)); + } else { + if (status == HttpStatus.SC_OK) { + try { + // try parsing json to verify response + // check if json contains maintenance and it should be false + String json = get.getResponseBodyAsString(); + return new JSONObject(json).getBoolean("maintenance"); + } catch (Exception e) { return true; } + } else { + return true; } } } catch (IOException e) { - Log_OC.e(TAG, "Error checking internet connection", e); + logger.e(TAG, "Error checking internet connection", e); } } else { return getActiveNetworkType() == JobRequest.NetworkType.ANY; diff --git a/src/main/java/com/nextcloud/client/network/NetworkModule.java b/src/main/java/com/nextcloud/client/network/NetworkModule.java index 61634af7a9..2a7edf8a7d 100644 --- a/src/main/java/com/nextcloud/client/network/NetworkModule.java +++ b/src/main/java/com/nextcloud/client/network/NetworkModule.java @@ -24,6 +24,7 @@ import android.content.Context; import android.net.ConnectivityManager; import com.nextcloud.client.account.UserAccountManager; +import com.nextcloud.client.logger.Logger; import javax.inject.Singleton; @@ -36,11 +37,13 @@ public class NetworkModule { @Provides ConnectivityService connectivityService(ConnectivityManager connectivityManager, UserAccountManager accountManager, - ClientFactory clientFactory) { + ClientFactory clientFactory, + Logger logger) { return new ConnectivityServiceImpl(connectivityManager, accountManager, clientFactory, - new ConnectivityServiceImpl.GetRequestBuilder()); + new ConnectivityServiceImpl.GetRequestBuilder(), + logger); } @Provides diff --git a/src/main/java/com/nextcloud/java/util/Optional.java b/src/main/java/com/nextcloud/java/util/Optional.java new file mode 100644 index 0000000000..e3a31e5c2a --- /dev/null +++ b/src/main/java/com/nextcloud/java/util/Optional.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.nextcloud.java.util; + +import com.nextcloud.java.util.function.Predicate; + +import java.util.NoSuchElementException; + +import androidx.core.util.Consumer; +import androidx.core.util.ObjectsCompat; +import androidx.core.util.Supplier; +import kotlin.jvm.functions.Function1; + +/** + * This class is backported from Java 8 to be used on older Android API levels. + * It uses available interfaces from Kotlin and androidx. It is semantically + * identical with Java 8 API, allowing smooth migration when those APIs become + * available. + * + * A container object which may or may not contain a non-null value. + * If a value is present, {@code isPresent()} will return {@code true} and + * {@code get()} will return the value. + * + *

Additional methods that depend on the presence or absence of a contained + * value are provided, such as {@link #orElse(java.lang.Object) orElse()} + * (return a default value if value not present) and + * {@link #ifPresent(Consumer) ifPresent()} (execute a block + * of code if the value is present). + */ +public final class Optional { + /** + * Common instance for {@code empty()}. + */ + private static final Optional EMPTY = new Optional<>(); + + /** + * If non-null, the value; if null, indicates no value is present + */ + private final T value; + + /** + * Constructs an empty instance. + * + * @implNote Generally only one empty instance, {@link Optional#EMPTY}, + * should exist per VM. + */ + private Optional() { + this.value = null; + } + + /** + * Returns an empty {@code Optional} instance. No value is present for this + * Optional. + * + * @apiNote Though it may be tempting to do so, avoid testing if an object + * is empty by comparing with {@code ==} against instances returned by + * {@code Option.empty()}. There is no guarantee that it is a singleton. + * Instead, use {@link #isPresent()}. + * + * @param Type of the non-existent value + * @return an empty {@code Optional} + */ + public static Optional empty() { + @SuppressWarnings("unchecked") + Optional t = (Optional) EMPTY; + return t; + } + + /** + * Constructs an instance with the value present. + * + * @param value the non-null value to be present + * @throws NullPointerException if value is null + */ + private Optional(T value) { + if (value == null) { + throw new NullPointerException(); + } + this.value = value; + } + + /** + * Returns an {@code Optional} with the specified present non-null value. + * + * @param the class of the value + * @param value the value to be present, which must be non-null + * @return an {@code Optional} with the value present + * @throws NullPointerException if value is null + */ + public static Optional of(T value) { + return new Optional<>(value); + } + + /** + * Returns an {@code Optional} describing the specified value, if non-null, + * otherwise returns an empty {@code Optional}. + * + * @param the class of the value + * @param value the possibly-null value to describe + * @return an {@code Optional} with a present value if the specified value + * is non-null, otherwise an empty {@code Optional} + */ + public static Optional ofNullable(T value) { + return value == null ? empty() : of(value); + } + + /** + * If a value is present in this {@code Optional}, returns the value, + * otherwise throws {@code NoSuchElementException}. + * + * @return the non-null value held by this {@code Optional} + * @throws NoSuchElementException if there is no value present + * + * @see Optional#isPresent() + */ + public T get() { + if (value == null) { + throw new NoSuchElementException("No value present"); + } + return value; + } + + /** + * Return {@code true} if there is a value present, otherwise {@code false}. + * + * @return {@code true} if there is a value present, otherwise {@code false} + */ + public boolean isPresent() { + return value != null; + } + + /** + * If a value is present, invoke the specified consumer with the value, + * otherwise do nothing. + * + * @param consumer block to be executed if a value is present + * @throws NullPointerException if value is present and {@code consumer} is + * null + */ + public void ifPresent(Consumer consumer) { + if (value != null) { + consumer.accept(value); + } + } + + /** + * If a value is present, and the value matches the given predicate, + * return an {@code Optional} describing the value, otherwise return an + * empty {@code Optional}. + * + * @param predicate a predicate to apply to the value, if present + * @return an {@code Optional} describing the value of this {@code Optional} + * if a value is present and the value matches the given predicate, + * otherwise an empty {@code Optional} + * @throws NullPointerException if the predicate is null + */ + public Optional filter(Predicate predicate) { + if (predicate == null) { + throw new NullPointerException(); + } + if (!isPresent()) { + return this; + } else { + return predicate.test(value) ? this : empty(); + } + } + + /** + * If a value is present, apply the provided mapping function to it, + * and if the result is non-null, return an {@code Optional} describing the + * result. Otherwise return an empty {@code Optional}. + * + * @apiNote This method supports post-processing on optional values, without + * the need to explicitly check for a return status. For example, the + * following code traverses a stream of file names, selects one that has + * not yet been processed, and then opens that file, returning an + * {@code Optional}: + * + *

{@code
+     *     Optional fis =
+     *         names.stream().filter(name -> !isProcessedYet(name))
+     *                       .findFirst()
+     *                       .map(name -> new FileInputStream(name));
+     * }
+ * + * Here, {@code findFirst} returns an {@code Optional}, and then + * {@code map} returns an {@code Optional} for the desired + * file if one exists. + * + * @param The type of the result of the mapping function + * @param mapper a mapping function to apply to the value, if present + * @return an {@code Optional} describing the result of applying a mapping + * function to the value of this {@code Optional}, if a value is present, + * otherwise an empty {@code Optional} + * @throws NullPointerException if the mapping function is null + */ + public Optional map(Function1 mapper) { + if (mapper == null) { + throw new NullPointerException(); + } + if (!isPresent()) { + return empty(); + } else { + return Optional.ofNullable(mapper.invoke(value)); + } + } + + /** + * If a value is present, apply the provided {@code Optional}-bearing + * mapping function to it, return that result, otherwise return an empty + * {@code Optional}. This method is similar to {@link #map(Function1)}, + * but the provided mapper is one whose result is already an {@code Optional}, + * and if invoked, {@code flatMap} does not wrap it with an additional + * {@code Optional}. + * + * @param The type parameter to the {@code Optional} returned by + * @param mapper a mapping function to apply to the value, if present + * the mapping function + * @return the result of applying an {@code Optional}-bearing mapping + * function to the value of this {@code Optional}, if a value is present, + * otherwise an empty {@code Optional} + * @throws NullPointerException if the mapping function is null or returns + * a null result + */ + public Optional flatMap(Function1> mapper) { + if(mapper == null) { + throw new NullPointerException(); + } + if (!isPresent()) { + return empty(); + } else { + Optional u = mapper.invoke(value); + if (u == null) { + throw new NullPointerException(); + } + return u; + } + } + + /** + * Return the value if present, otherwise return {@code other}. + * + * @param other the value to be returned if there is no value present, may + * be null + * @return the value, if present, otherwise {@code other} + */ + public T orElse(T other) { + return value != null ? value : other; + } + + /** + * Return the value if present, otherwise invoke {@code other} and return + * the result of that invocation. + * + * @param other a {@code Supplier} whose result is returned if no value + * is present + * @return the value if present otherwise the result of {@code other.get()} + * @throws NullPointerException if value is not present and {@code other} is + * null + */ + public T orElseGet(Supplier other) { + return value != null ? value : other.get(); + } + + /** + * Return the contained value, if present, otherwise throw an exception + * to be created by the provided supplier. + * + * @apiNote A method reference to the exception constructor with an empty + * argument list can be used as the supplier. For example, + * {@code IllegalStateException::new} + * + * @param Type of the exception to be thrown + * @param exceptionSupplier The supplier which will return the exception to + * be thrown + * @return the present value + * @throws X if there is no value present + * @throws NullPointerException if no value is present and + * {@code exceptionSupplier} is null + */ + public T orElseThrow(Supplier exceptionSupplier) throws X { + if (value != null) { + return value; + } else { + throw exceptionSupplier.get(); + } + } + + /** + * Indicates whether some other object is "equal to" this Optional. The + * other object is considered equal if: + *
    + *
  • it is also an {@code Optional} and; + *
  • both instances have no value present or; + *
  • the present values are "equal to" each other via {@code equals()}. + *
+ * + * @param obj an object to be tested for equality + * @return {code true} if the other object is "equal to" this object + * otherwise {@code false} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof Optional)) { + return false; + } + + Optional other = (Optional) obj; + return ObjectsCompat.equals(value, other.value); + } + + /** + * Returns the hash code value of the present value, if any, or 0 (zero) if + * no value is present. + * + * @return hash code value of the present value or 0 if no value is present + */ + @Override + public int hashCode() { + return ObjectsCompat.hashCode(value); + } + + /** + * Returns a non-empty string representation of this Optional suitable for + * debugging. The exact presentation format is unspecified and may vary + * between implementations and versions. + * + * @implSpec If a value is present the result must include its string + * representation in the result. Empty and present Optionals must be + * unambiguously differentiable. + * + * @return the string representation of this instance + */ + @Override + public String toString() { + return value != null + ? String.format("Optional[%s]", value) + : "Optional.empty"; + } +} diff --git a/src/main/java/com/nextcloud/java/util/function/Predicate.java b/src/main/java/com/nextcloud/java/util/function/Predicate.java new file mode 100644 index 0000000000..857ba3d3b1 --- /dev/null +++ b/src/main/java/com/nextcloud/java/util/function/Predicate.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.nextcloud.java.util.function; + +/** + * This class is backported from Java 8 to be used on older Android API levels. + * + * Represents a predicate (boolean-valued function) of one argument. + * + *

This is a functional interface + * whose functional method is {@link #test(Object)}. + * + * @param the type of the input to the predicate + */ +@FunctionalInterface +public interface Predicate { + + /** + * Evaluates this predicate on the given argument. + * + * @param t the input argument + * @return {@code true} if the input argument matches the predicate, + * otherwise {@code false} + */ + boolean test(T t); + + /** + * Returns a composed predicate that represents a short-circuiting logical + * AND of this predicate and another. When evaluating the composed + * predicate, if this predicate is {@code false}, then the {@code other} + * predicate is not evaluated. + * + *

Any exceptions thrown during evaluation of either predicate are relayed + * to the caller; if evaluation of this predicate throws an exception, the + * {@code other} predicate will not be evaluated. + * + * @param other a predicate that will be logically-ANDed with this + * predicate + * @return a composed predicate that represents the short-circuiting logical + * AND of this predicate and the {@code other} predicate + * @throws NullPointerException if other is null + */ + default Predicate and(Predicate other) { + if (other == null) { + throw new NullPointerException(); + } + return (t) -> test(t) && other.test(t); + } + + /** + * Returns a predicate that represents the logical negation of this + * predicate. + * + * @return a predicate that represents the logical negation of this + * predicate + */ + default Predicate negate() { + return (t) -> !test(t); + } + + /** + * Returns a composed predicate that represents a short-circuiting logical + * OR of this predicate and another. When evaluating the composed + * predicate, if this predicate is {@code true}, then the {@code other} + * predicate is not evaluated. + * + *

Any exceptions thrown during evaluation of either predicate are relayed + * to the caller; if evaluation of this predicate throws an exception, the + * {@code other} predicate will not be evaluated. + * + * @param other a predicate that will be logically-ORed with this + * predicate + * @return a composed predicate that represents the short-circuiting logical + * OR of this predicate and the {@code other} predicate + * @throws NullPointerException if other is null + */ + default Predicate or(Predicate other) { + if (other == null) { + throw new NullPointerException(); + } + return (t) -> test(t) || other.test(t); + } + + /** + * Returns a predicate that tests if two arguments are equal according + * to {@link androidx.core.util.ObjectsCompat#equals(Object, Object)}. + * + * @param the type of arguments to the predicate + * @param targetRef the object reference with which to compare for equality, + * which may be {@code null} + * @return a predicate that tests if two arguments are equal according + * to {@link androidx.core.util.ObjectsCompat#equals(Object, Object)} + */ + static Predicate isEqual(Object targetRef) { + return (null == targetRef) + ? object -> object == null + : object -> targetRef.equals(object); + } +} diff --git a/src/main/java/com/nextcloud/java/util/package-info.java b/src/main/java/com/nextcloud/java/util/package-info.java new file mode 100644 index 0000000000..fd71fbc2d3 --- /dev/null +++ b/src/main/java/com/nextcloud/java/util/package-info.java @@ -0,0 +1,26 @@ +/* + * Nextcloud Android client application + * + * @author Chris Narkiewicz + * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * This is a compatibility package providing some backported Java 8 classes + * not available in some older Android runtimes. + */ +package com.nextcloud.java.util; diff --git a/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java index d31313f378..b8130bcdc4 100644 --- a/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java @@ -30,6 +30,7 @@ import android.database.Cursor; import android.net.Uri; import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.account.User; import com.owncloud.android.db.OCUpload; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; import com.owncloud.android.db.UploadResult; @@ -345,24 +346,19 @@ public class UploadsStorageManager extends Observable { } public OCUpload[] getCurrentAndPendingUploadsForCurrentAccount() { - Account account = currentAccountProvider.getCurrentAccount(); + User user = currentAccountProvider.getUser(); - if (account != null) { - return getUploads( - ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + - " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + - "==" + UploadResult.DELAYED_FOR_WIFI.getValue() + - " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + - "==" + UploadResult.LOCK_FAILED.getValue() + - " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + - "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() + - " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + - "==" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + - " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", - account.name); - } else { - return new OCUpload[0]; - } + return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + + "==" + UploadResult.DELAYED_FOR_WIFI.getValue() + + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + + "==" + UploadResult.LOCK_FAILED.getValue() + + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + + "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() + + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + + "==" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + + " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", + user.getAccountName()); } /** @@ -384,14 +380,10 @@ public class UploadsStorageManager extends Observable { } public OCUpload[] getFinishedUploadsForCurrentAccount() { - Account account = currentAccountProvider.getCurrentAccount(); + User user = currentAccountProvider.getUser(); - if (account != null) { - return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND + - ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", account.name); - } else { - return new OCUpload[0]; - } + return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", user.getAccountName()); } /** @@ -403,23 +395,19 @@ public class UploadsStorageManager extends Observable { } public OCUpload[] getFailedButNotDelayedUploadsForCurrentAccount() { - Account account = currentAccountProvider.getCurrentAccount(); + User user = currentAccountProvider.getUser(); - if (account != null) { - return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.LOCK_FAILED.getValue() + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + - AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", - account.name); - } else { - return new OCUpload[0]; - } + return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.LOCK_FAILED.getValue() + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + + AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", + user.getAccountName()); } /** @@ -446,50 +434,41 @@ public class UploadsStorageManager extends Observable { } public long clearFailedButNotDelayedUploads() { - Account account = currentAccountProvider.getCurrentAccount(); - - long result = 0; - if (account != null) { - result = getDB().delete( - ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.LOCK_FAILED.getValue() + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + - "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + - AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", - new String[]{account.name} - ); - } - + User user = currentAccountProvider.getUser(); + final long deleted = getDB().delete( + ProviderTableMeta.CONTENT_URI_UPLOADS, + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.LOCK_FAILED.getValue() + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + + "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + + AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", + new String[]{user.getAccountName()} + ); Log_OC.d(TAG, "delete all failed uploads but those delayed for Wifi"); - if (result > 0) { + if (deleted > 0) { notifyObserversNow(); } - return result; + return deleted; } public long clearSuccessfulUploads() { - Account account = currentAccountProvider.getCurrentAccount(); - - long result = 0; - if (account != null) { - result = getDB().delete( - ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND + - ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{account.name} - ); - } + User user = currentAccountProvider.getUser(); + final long deleted = getDB().delete( + ProviderTableMeta.CONTENT_URI_UPLOADS, + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{user.getAccountName()} + ); Log_OC.d(TAG, "delete all successful uploads"); - if (result > 0) { + if (deleted > 0) { notifyObserversNow(); } - return result; + return deleted; } /** diff --git a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 597c1e6601..ec947a8fb2 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -47,6 +47,7 @@ import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.target.BitmapImageViewTarget; +import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.preferences.AppPreferences; import com.owncloud.android.R; @@ -117,7 +118,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter checkedFiles; private FileDataStorageManager mStorageManager; - private Account account; + private User user; private OCFileListFragmentInterface ocFileListFragmentInterface; private FilesFilter mFilesFilter; @@ -135,7 +136,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter(); this.transferServiceGetter = transferServiceGetter; - if (account != null) { + if (this.user != null) { AccountManager platformAccountManager = AccountManager.get(mContext); - userId = platformAccountManager.getUserData(account, + userId = platformAccountManager.getUserData(this.user.toPlatformAccount(), com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); } else { userId = ""; @@ -399,7 +400,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter ocFileListFragmentInterface.onShareIconClick(file)); } else { sharedIconView.setOnClickListener(view -> ocFileListFragmentInterface.showShareDetailView(file)); @@ -703,7 +708,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter