Improve handling of network timeouts for donor badges.

fork-5.53.8
Alex Hart 2021-11-05 15:21:27 -03:00 zatwierdzone przez Greyson Parrelli
rodzic c4164b17a2
commit 482a10de02
9 zmienionych plików z 74 dodań i 44 usunięć

Wyświetl plik

@ -13,8 +13,11 @@ class BadgeRepository(context: Context) {
private val context = context.applicationContext
fun setVisibilityForAllBadges(displayBadgesOnProfile: Boolean): Completable = Completable.fromAction {
val badges = Recipient.self().badges.map { it.copy(visible = displayBadgesOnProfile) }
fun setVisibilityForAllBadges(
displayBadgesOnProfile: Boolean,
selfBadges: List<Badge> = Recipient.self().badges
): Completable = Completable.fromAction {
val badges = selfBadges.map { it.copy(visible = displayBadgesOnProfile) }
ProfileUtil.uploadProfileWithBadges(context, badges)
val recipientDatabase: RecipientDatabase = DatabaseFactory.getRecipientDatabase(context)

Wyświetl plik

@ -154,18 +154,30 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
val subscriber = SignalStore.donationsValues().requireSubscriber()
Log.d(TAG, "Attempting to set user subscription level to $subscriptionLevel", true)
ApplicationDependencies.getDonationsService().updateSubscriptionLevel(
subscriber.subscriberId,
subscriptionLevel,
subscriber.currencyCode,
levelUpdateOperation.idempotencyKey.serialize()
).flatMap(ServiceResponse<EmptyResponse>::flattenResult).ignoreElement().andThen {
Log.d(TAG, "Successfully set user subscription to level $subscriptionLevel", true)
SignalStore.donationsValues().clearUserManuallyCancelled()
SignalStore.donationsValues().clearLevelOperation()
LevelUpdate.updateProcessingState(false)
it.onComplete()
levelUpdateOperation.idempotencyKey.serialize(),
SubscriptionReceiptRequestResponseJob.MUTEX
).flatMapCompletable {
if (it.status == 200 || it.status == 204) {
Log.d(TAG, "Successfully set user subscription to level $subscriptionLevel with response code ${it.status}", true)
SignalStore.donationsValues().clearUserManuallyCancelled()
SignalStore.donationsValues().clearLevelOperations()
LevelUpdate.updateProcessingState(false)
Completable.complete()
} else {
if (it.applicationError.isPresent) {
Log.w(TAG, "Failed to set user subscription to level $subscriptionLevel with response code ${it.status}", it.applicationError.get(), true)
SignalStore.donationsValues().clearLevelOperations()
} else {
Log.w(TAG, "Failed to set user subscription to level $subscriptionLevel", it.executionError.orNull(), true)
}
LevelUpdate.updateProcessingState(false)
it.flattenResult().ignoreElement()
}
}.andThen {
Log.d(TAG, "Enqueuing request response job chain.", true)
val jobId = SubscriptionReceiptRequestResponseJob.enqueueSubscriptionContinuation()
@ -205,14 +217,13 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
}
}
}.doOnError {
SignalStore.donationsValues().clearLevelOperation()
LevelUpdate.updateProcessingState(false)
}.subscribeOn(Schedulers.io())
}
private fun getOrCreateLevelUpdateOperation(subscriptionLevel: String): Single<LevelUpdateOperation> = Single.fromCallable {
val levelUpdateOperation = SignalStore.donationsValues().getLevelOperation()
if (levelUpdateOperation == null || subscriptionLevel != levelUpdateOperation.level) {
val levelUpdateOperation = SignalStore.donationsValues().getLevelOperation(subscriptionLevel)
if (levelUpdateOperation == null) {
val newOperation = LevelUpdateOperation(
idempotencyKey = IdempotencyKey.generate(),
level = subscriptionLevel

Wyświetl plik

@ -145,7 +145,7 @@ class SubscribeViewModel(
onComplete = {
eventPublisher.onNext(DonationEvent.SubscriptionCancelled)
SignalStore.donationsValues().setLastEndOfPeriod(0L)
SignalStore.donationsValues().clearLevelOperation()
SignalStore.donationsValues().clearLevelOperations()
SignalStore.donationsValues().markUserManuallyCancelled()
refreshActiveSubscription()
store.update { it.copy(stage = SubscribeState.Stage.READY) }

Wyświetl plik

@ -150,7 +150,7 @@ class ThanksForYourSupportBottomSheetDialogFragment : FixedRoundedCornerBottomSh
findNavController().popBackStack()
} else {
requireActivity().finish()
requireActivity().startActivity(AppSettingsActivity.subscriptions(requireContext()))
requireActivity().startActivity(AppSettingsActivity.manageSubscriptions(requireContext()))
}
}

Wyświetl plik

@ -224,14 +224,7 @@ public class RefreshOwnProfileJob extends BaseJob {
Log.d(TAG, "Detected mixed visibility of badges. Telling the server to mark them all visible.", true);
BadgeRepository badgeRepository = new BadgeRepository(context);
badgeRepository.setVisibilityForAllBadges(true);
DatabaseFactory.getRecipientDatabase(context)
.setBadges(Recipient.self().getId(),
appBadges.stream()
.map(Badge::setVisible)
.collect(Collectors.toList()));
badgeRepository.setVisibilityForAllBadges(true, appBadges).blockingSubscribe();
} else {
DatabaseFactory.getRecipientDatabase(context)
.setBadges(Recipient.self().getId(), appBadges);

Wyświetl plik

@ -41,6 +41,8 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
private static final String DATA_REQUEST_BYTES = "data.request.bytes";
private static final String DATA_SUBSCRIBER_ID = "data.subscriber.id";
public static final Object MUTEX = new Object();
private ReceiptCredentialRequestContext requestContext;
private final SubscriberId subscriberId;
@ -107,6 +109,12 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
@Override
protected void onRun() throws Exception {
synchronized (MUTEX) {
doRun();
}
}
private void doRun() throws Exception {
ActiveSubscription.Subscription subscription = getLatestSubscriptionInformation();
if (subscription == null || !subscription.isActive()) {
Log.d(TAG, "User does not have an active subscription. Exiting.", true);

Wyświetl plik

@ -23,12 +23,12 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
private const val KEY_SUBSCRIPTION_CURRENCY_CODE = "donation.currency.code"
private const val KEY_CURRENCY_CODE_BOOST = "donation.currency.code.boost"
private const val KEY_SUBSCRIBER_ID_PREFIX = "donation.subscriber.id."
private const val KEY_IDEMPOTENCY = "donation.idempotency.key"
private const val KEY_LEVEL = "donation.level"
private const val KEY_LAST_KEEP_ALIVE_LAUNCH = "donation.last.successful.ping"
private const val KEY_LAST_END_OF_PERIOD = "donation.last.end.of.period"
private const val EXPIRED_BADGE = "donation.expired.badge"
private const val USER_MANUALLY_CANCELLED = "donation.user.manually.cancelled"
private const val KEY_LEVEL_OPERATION_PREFIX = "donation.level.operation."
private const val KEY_LEVEL_HISTORY = "donation.level.history"
}
override fun onFirstEverAppLaunch() = Unit
@ -114,29 +114,36 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
subscriptionCurrencyPublisher.onNext(Currency.getInstance(currencyCode))
}
fun getLevelOperation(): LevelUpdateOperation? {
val level = getString(KEY_LEVEL, null)
val idempotencyKey = getBlob(KEY_IDEMPOTENCY, null)?.let { IdempotencyKey.fromBytes(it) }
return if (level == null || idempotencyKey == null) {
null
fun getLevelOperation(level: String): LevelUpdateOperation? {
val idempotencyKey = getBlob("${KEY_LEVEL_OPERATION_PREFIX}$level", null)
return if (idempotencyKey != null) {
LevelUpdateOperation(IdempotencyKey.fromBytes(idempotencyKey), level)
} else {
LevelUpdateOperation(idempotencyKey, level)
null
}
}
fun setLevelOperation(levelUpdateOperation: LevelUpdateOperation) {
store.beginWrite()
.putString(KEY_LEVEL, levelUpdateOperation.level)
.putBlob(KEY_IDEMPOTENCY, levelUpdateOperation.idempotencyKey.bytes)
.apply()
addLevelToHistory(levelUpdateOperation.level)
putBlob("$KEY_LEVEL_OPERATION_PREFIX${levelUpdateOperation.level}", levelUpdateOperation.idempotencyKey.bytes)
}
fun clearLevelOperation() {
store.beginWrite()
.remove(KEY_LEVEL)
.remove(KEY_IDEMPOTENCY)
.apply()
private fun getLevelHistory(): Set<String> {
return getString(KEY_LEVEL_HISTORY, "").split(",").toSet()
}
private fun addLevelToHistory(level: String) {
val levels = getLevelHistory() + level
putString(KEY_LEVEL_HISTORY, levels.joinToString(","))
}
fun clearLevelOperations() {
val levelHistory = getLevelHistory()
val write = store.beginWrite()
for (level in levelHistory) {
write.remove("${KEY_LEVEL_OPERATION_PREFIX}$level")
}
write.apply()
}
fun setExpiredBadge(badge: Badge?) {

Wyświetl plik

@ -427,7 +427,7 @@ public final class FeatureFlags {
* Whether or not donor badges should be displayed throughout the app.
*/
public static boolean displayDonorBadges() {
return getBoolean(DONOR_BADGES_DISPLAY, false);
return getBoolean(DONOR_BADGES_DISPLAY, Environment.IS_STAGING);
}
public static boolean cdsh() {

Wyświetl plik

@ -125,10 +125,18 @@ public class DonationsService {
* @param level The new level to subscribe to
* @param currencyCode The currencyCode the user is using for payment
* @param idempotencyKey url-safe-base64-encoded random 16-byte value (see description)
* @param mutex A mutex to lock on to avoid a situation where this subscription update happens *as* we are trying to get a credential receipt.
*/
public Single<ServiceResponse<EmptyResponse>> updateSubscriptionLevel(SubscriberId subscriberId, String level, String currencyCode, String idempotencyKey) {
public Single<ServiceResponse<EmptyResponse>> updateSubscriptionLevel(SubscriberId subscriberId,
String level,
String currencyCode,
String idempotencyKey,
Object mutex
) {
return createServiceResponse(() -> {
pushServiceSocket.updateSubscriptionLevel(subscriberId.serialize(), level, currencyCode, idempotencyKey);
synchronized(mutex) {
pushServiceSocket.updateSubscriptionLevel(subscriberId.serialize(), level, currencyCode, idempotencyKey);
}
return new Pair<>(EmptyResponse.INSTANCE, 200);
});
}