Convert PendingUploadsPresenter and contract to Kotlin with some code-cleanup

This commit is contained in:
Paul Hawke 2024-12-11 09:52:24 -06:00
parent 9160f2fe63
commit a8740c4df8
6 changed files with 354 additions and 389 deletions

View file

@ -10,6 +10,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.contributions.Contribution import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_FAILED
import fr.free.nrw.commons.databinding.FragmentFailedUploadsBinding import fr.free.nrw.commons.databinding.FragmentFailedUploadsBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.media.MediaClient import fr.free.nrw.commons.media.MediaClient
@ -43,7 +44,7 @@ class FailedUploadsFragment :
private lateinit var adapter: FailedUploadsAdapter private lateinit var adapter: FailedUploadsAdapter
var contributionsList = ArrayList<Contribution>() var contributionsList = mutableListOf<Contribution>()
private lateinit var uploadProgressActivity: UploadProgressActivity private lateinit var uploadProgressActivity: UploadProgressActivity
@ -71,7 +72,7 @@ class FailedUploadsFragment :
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
): View? { ): View {
binding = FragmentFailedUploadsBinding.inflate(layoutInflater) binding = FragmentFailedUploadsBinding.inflate(layoutInflater)
pendingUploadsPresenter.onAttachView(this) pendingUploadsPresenter.onAttachView(this)
initAdapter() initAdapter()
@ -99,9 +100,9 @@ class FailedUploadsFragment :
pendingUploadsPresenter.getFailedContributions() pendingUploadsPresenter.getFailedContributions()
pendingUploadsPresenter.failedContributionList.observe( pendingUploadsPresenter.failedContributionList.observe(
viewLifecycleOwner, viewLifecycleOwner,
) { list: PagedList<Contribution?> -> ) { list: PagedList<Contribution> ->
adapter.submitList(list) adapter.submitList(list)
contributionsList = ArrayList() contributionsList = mutableListOf()
list.forEach { list.forEach {
if (it != null) { if (it != null) {
contributionsList.add(it) contributionsList.add(it)
@ -124,26 +125,22 @@ class FailedUploadsFragment :
* Restarts all the failed uploads. * Restarts all the failed uploads.
*/ */
fun restartUploads() { fun restartUploads() {
if (contributionsList != null) { pendingUploadsPresenter.restartUploads(
pendingUploadsPresenter.restartUploads( contributionsList,
contributionsList, 0,
0, requireContext().applicationContext,
this.requireContext().applicationContext, )
)
}
} }
/** /**
* Restarts a specific upload. * Restarts a specific upload.
*/ */
override fun restartUpload(index: Int) { override fun restartUpload(index: Int) {
if (contributionsList != null) { pendingUploadsPresenter.restartUpload(
pendingUploadsPresenter.restartUpload( contributionsList,
contributionsList, index,
index, requireContext().applicationContext,
this.requireContext().applicationContext, )
)
}
} }
/** /**
@ -166,7 +163,7 @@ class FailedUploadsFragment :
ViewUtil.showShortToast(context, R.string.cancelling_upload) ViewUtil.showShortToast(context, R.string.cancelling_upload)
pendingUploadsPresenter.deleteUpload( pendingUploadsPresenter.deleteUpload(
contribution, contribution,
this.requireContext().applicationContext, requireContext().applicationContext,
) )
}, },
{}, {},
@ -177,28 +174,24 @@ class FailedUploadsFragment :
* Deletes all the uploads after getting a confirmation from the user using Dialog. * Deletes all the uploads after getting a confirmation from the user using Dialog.
*/ */
fun deleteUploads() { fun deleteUploads() {
if (contributionsList != null) { DialogUtil.showAlertDialog(
DialogUtil.showAlertDialog( requireActivity(),
requireActivity(), String.format(
String.format( Locale.getDefault(),
Locale.getDefault(), requireActivity().getString(R.string.cancelling_all_the_uploads),
requireActivity().getString(R.string.cancelling_all_the_uploads), ),
), String.format(
String.format( Locale.getDefault(),
Locale.getDefault(), requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads),
requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads), ),
), String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)), String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)), {
{ ViewUtil.showShortToast(context, R.string.cancelling_upload)
ViewUtil.showShortToast(context, R.string.cancelling_upload) uploadProgressActivity.hidePendingIcons()
uploadProgressActivity.hidePendingIcons() pendingUploadsPresenter.deleteUploads(listOf(STATE_FAILED))
pendingUploadsPresenter.deleteUploads( },
listOf(Contribution.STATE_FAILED), {},
) )
},
{},
)
}
} }
} }

View file

@ -1,31 +0,0 @@
package fr.free.nrw.commons.upload;
import android.content.Context;
import fr.free.nrw.commons.BasePresenter;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract.View;
/**
* The contract using which the PendingUploadsFragment or FailedUploadsFragment would communicate
* with its PendingUploadsPresenter
*/
public class PendingUploadsContract {
/**
* Interface representing the view for uploads.
*/
public interface View { }
/**
* Interface representing the user actions related to uploads.
*/
public interface UserActionListener extends
BasePresenter<fr.free.nrw.commons.upload.PendingUploadsContract.View> {
/**
* Deletes a upload.
*/
void deleteUpload(Contribution contribution, Context context);
}
}

View file

@ -0,0 +1,26 @@
package fr.free.nrw.commons.upload
import android.content.Context
import fr.free.nrw.commons.BasePresenter
import fr.free.nrw.commons.contributions.Contribution
/**
* The contract using which the PendingUploadsFragment or FailedUploadsFragment would communicate
* with its PendingUploadsPresenter
*/
class PendingUploadsContract {
/**
* Interface representing the view for uploads.
*/
interface View
/**
* Interface representing the user actions related to uploads.
*/
interface UserActionListener : BasePresenter<View> {
/**
* Deletes a upload.
*/
fun deleteUpload(contribution: Contribution?, context: Context?)
}
}

View file

@ -10,6 +10,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.Contribution import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_IN_PROGRESS
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_PAUSED
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_QUEUED
import fr.free.nrw.commons.databinding.FragmentPendingUploadsBinding import fr.free.nrw.commons.databinding.FragmentPendingUploadsBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
@ -35,7 +38,8 @@ class PendingUploadsFragment :
private lateinit var adapter: PendingUploadsAdapter private lateinit var adapter: PendingUploadsAdapter
private var contributionsSize = 0 private var contributionsSize = 0
var contributionsList = ArrayList<Contribution>()
private var contributionsList = mutableListOf<Contribution>()
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
@ -48,7 +52,7 @@ class PendingUploadsFragment :
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
): View? { ): View {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = FragmentPendingUploadsBinding.inflate(inflater, container, false) binding = FragmentPendingUploadsBinding.inflate(inflater, container, false)
pendingUploadsPresenter.onAttachView(this) pendingUploadsPresenter.onAttachView(this)
@ -71,27 +75,24 @@ class PendingUploadsFragment :
/** /**
* Initializes the recycler view. * Initializes the recycler view.
*/ */
fun initRecyclerView() { private fun initRecyclerView() {
binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
binding.pendingUploadsRecyclerView.adapter = adapter binding.pendingUploadsRecyclerView.adapter = adapter
pendingUploadsPresenter.setup() pendingUploadsPresenter.setup()
pendingUploadsPresenter.totalContributionList.observe( pendingUploadsPresenter.totalContributionList
viewLifecycleOwner, .observe(viewLifecycleOwner) { list: PagedList<Contribution> ->
) { list: PagedList<Contribution?> ->
contributionsSize = list.size contributionsSize = list.size
contributionsList = ArrayList() contributionsList = mutableListOf()
var pausedOrQueuedUploads = 0 var pausedOrQueuedUploads = 0
list.forEach { list.forEach {
if (it != null) { if (it != null) {
if (it.state == Contribution.STATE_PAUSED || if (it.state == STATE_PAUSED ||
it.state == Contribution.STATE_QUEUED || it.state == STATE_QUEUED ||
it.state == Contribution.STATE_IN_PROGRESS it.state == STATE_IN_PROGRESS
) { ) {
contributionsList.add(it) contributionsList.add(it)
} }
if (it.state == Contribution.STATE_PAUSED || if (it.state == STATE_PAUSED || it.state == STATE_QUEUED) {
it.state == Contribution.STATE_QUEUED
) {
pausedOrQueuedUploads++ pausedOrQueuedUploads++
} }
} }
@ -104,7 +105,7 @@ class PendingUploadsFragment :
binding.nopendingTextView.visibility = View.GONE binding.nopendingTextView.visibility = View.GONE
binding.pendingUplaodsLl.visibility = View.VISIBLE binding.pendingUplaodsLl.visibility = View.VISIBLE
adapter.submitList(list) adapter.submitList(list)
binding.progressTextView.setText(contributionsSize.toString() + " uploads left") binding.progressTextView.setText("$contributionsSize uploads left")
if ((pausedOrQueuedUploads == contributionsSize) || CommonsApplication.isPaused) { if ((pausedOrQueuedUploads == contributionsSize) || CommonsApplication.isPaused) {
uploadProgressActivity.setPausedIcon(true) uploadProgressActivity.setPausedIcon(true)
} else { } else {
@ -118,23 +119,18 @@ class PendingUploadsFragment :
* Cancels a specific upload after getting a confirmation from the user using Dialog. * Cancels a specific upload after getting a confirmation from the user using Dialog.
*/ */
override fun deleteUpload(contribution: Contribution?) { override fun deleteUpload(contribution: Contribution?) {
val activity = requireActivity()
val locale = Locale.getDefault()
showAlertDialog( showAlertDialog(
requireActivity(), activity,
String.format( String.format(locale, activity.getString(R.string.cancelling_upload)),
Locale.getDefault(), String.format(locale, activity.getString(R.string.cancel_upload_dialog)),
requireActivity().getString(R.string.cancelling_upload), String.format(locale, activity.getString(R.string.yes)),
), String.format(locale, activity.getString(R.string.no)),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.cancel_upload_dialog),
),
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
{ {
ViewUtil.showShortToast(context, R.string.cancelling_upload) ViewUtil.showShortToast(context, R.string.cancelling_upload)
pendingUploadsPresenter.deleteUpload( pendingUploadsPresenter.deleteUpload(
contribution, contribution, requireContext().applicationContext,
this.requireContext().applicationContext,
) )
}, },
{}, {},
@ -144,47 +140,35 @@ class PendingUploadsFragment :
/** /**
* Restarts all the paused uploads. * Restarts all the paused uploads.
*/ */
fun restartUploads() { fun restartUploads() = pendingUploadsPresenter.restartUploads(
if (contributionsList != null) { contributionsList, 0, requireContext().applicationContext
pendingUploadsPresenter.restartUploads( )
contributionsList,
0,
this.requireContext().applicationContext,
)
}
}
/** /**
* Pauses all the ongoing uploads. * Pauses all the ongoing uploads.
*/ */
fun pauseUploads() { fun pauseUploads() = pendingUploadsPresenter.pauseUploads()
pendingUploadsPresenter.pauseUploads()
}
/** /**
* Cancels all the uploads after getting a confirmation from the user using Dialog. * Cancels all the uploads after getting a confirmation from the user using Dialog.
*/ */
fun deleteUploads() { fun deleteUploads() {
val activity = requireActivity()
val locale = Locale.getDefault()
showAlertDialog( showAlertDialog(
requireActivity(), activity,
String.format( String.format(locale, activity.getString(R.string.cancelling_all_the_uploads)),
Locale.getDefault(), String.format(locale, activity.getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads)),
requireActivity().getString(R.string.cancelling_all_the_uploads), String.format(locale, activity.getString(R.string.yes)),
), String.format(locale, activity.getString(R.string.no)),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads),
),
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
{ {
ViewUtil.showShortToast(context, R.string.cancelling_upload) ViewUtil.showShortToast(context, R.string.cancelling_upload)
uploadProgressActivity.hidePendingIcons() uploadProgressActivity.hidePendingIcons()
pendingUploadsPresenter.deleteUploads( pendingUploadsPresenter.deleteUploads(
listOf( listOf(
Contribution.STATE_QUEUED, STATE_QUEUED,
Contribution.STATE_IN_PROGRESS, STATE_IN_PROGRESS,
Contribution.STATE_PAUSED, STATE_PAUSED,
), ),
) )
}, },

View file

@ -1,263 +0,0 @@
package fr.free.nrw.commons.upload;
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.paging.DataSource.Factory;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
import androidx.work.ExistingWorkPolicy;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionBoundaryCallback;
import fr.free.nrw.commons.contributions.ContributionsRemoteDataSource;
import fr.free.nrw.commons.contributions.ContributionsRepository;
import fr.free.nrw.commons.di.CommonsApplicationModule;
import fr.free.nrw.commons.repository.UploadRepository;
import fr.free.nrw.commons.upload.PendingUploadsContract.UserActionListener;
import fr.free.nrw.commons.upload.PendingUploadsContract.View;
import fr.free.nrw.commons.upload.worker.WorkRequestHelper;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
/**
* The presenter class for PendingUploadsFragment and FailedUploadsFragment
*/
public class PendingUploadsPresenter implements UserActionListener {
private final ContributionBoundaryCallback contributionBoundaryCallback;
private final ContributionsRepository contributionsRepository;
private final UploadRepository uploadRepository;
private final Scheduler ioThreadScheduler;
private final CompositeDisposable compositeDisposable;
private final ContributionsRemoteDataSource contributionsRemoteDataSource;
LiveData<PagedList<Contribution>> totalContributionList;
LiveData<PagedList<Contribution>> failedContributionList;
@Inject
PendingUploadsPresenter(
final ContributionBoundaryCallback contributionBoundaryCallback,
final ContributionsRemoteDataSource contributionsRemoteDataSource,
final ContributionsRepository contributionsRepository,
final UploadRepository uploadRepository,
@Named(IO_THREAD) final Scheduler ioThreadScheduler) {
this.contributionBoundaryCallback = contributionBoundaryCallback;
this.contributionsRepository = contributionsRepository;
this.uploadRepository = uploadRepository;
this.ioThreadScheduler = ioThreadScheduler;
this.contributionsRemoteDataSource = contributionsRemoteDataSource;
compositeDisposable = new CompositeDisposable();
}
/**
* Setups the paged list of Pending Uploads. This method sets the configuration for paged list
* and ties it up with the live data object. This method can be tweaked to update the lazy
* loading behavior of the contributions list
*/
void setup() {
final PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setPrefetchDistance(50)
.setPageSize(10).build();
Factory<Integer, Contribution> factory;
factory = contributionsRepository.fetchContributionsWithStatesSortedByDateUploadStarted(
Arrays.asList(Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS,
Contribution.STATE_PAUSED));
LivePagedListBuilder livePagedListBuilder = new LivePagedListBuilder(factory,
pagedListConfig);
totalContributionList = livePagedListBuilder.build();
}
/**
* Setups the paged list of Failed Uploads. This method sets the configuration for paged list
* and ties it up with the live data object. This method can be tweaked to update the lazy
* loading behavior of the contributions list
*/
void getFailedContributions() {
final PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setPrefetchDistance(50)
.setPageSize(10).build();
Factory<Integer, Contribution> factory;
factory = contributionsRepository.fetchContributionsWithStatesSortedByDateUploadStarted(
Collections.singletonList(Contribution.STATE_FAILED));
LivePagedListBuilder livePagedListBuilder = new LivePagedListBuilder(factory,
pagedListConfig);
failedContributionList = livePagedListBuilder.build();
}
@Override
public void onAttachView(@NonNull View view) {
}
@Override
public void onDetachView() {
compositeDisposable.clear();
contributionsRemoteDataSource.dispose();
contributionBoundaryCallback.dispose();
}
/**
* Deletes the specified upload (contribution) from the database.
*
* @param contribution The contribution object representing the upload to be deleted.
* @param context The context in which the operation is being performed.
*/
@Override
public void deleteUpload(final Contribution contribution, Context context) {
compositeDisposable.add(contributionsRepository
.deleteContributionFromDB(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
/**
* Pauses all the uploads by changing the state of contributions from STATE_QUEUED and
* STATE_IN_PROGRESS to STATE_PAUSED in the database.
*/
public void pauseUploads() {
CommonsApplication.isPaused = true;
compositeDisposable.add(contributionsRepository
.updateContributionsWithStates(
List.of(Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS),
Contribution.STATE_PAUSED)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
/**
* Deletes contributions from the database that match the specified states.
*
* @param states A list of integers representing the states of the contributions to be deleted.
*/
public void deleteUploads(List<Integer> states) {
compositeDisposable.add(contributionsRepository
.deleteContributionsFromDBWithStates(states)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
/**
* Restarts the uploads for the specified list of contributions starting from the given index.
*
* @param contributionList The list of contributions to be restarted.
* @param index The starting index in the list from which to restart uploads.
* @param context The context in which the operation is being performed.
*/
public void restartUploads(List<Contribution> contributionList, int index, Context context) {
CommonsApplication.isPaused = false;
if (index >= contributionList.size()) {
return;
}
Contribution it = contributionList.get(index);
if (it.getState() == Contribution.STATE_FAILED) {
it.setDateUploadStarted(Calendar.getInstance().getTime());
if (it.getErrorInfo() == null) {
it.setChunkInfo(null);
it.setTransferred(0);
}
compositeDisposable.add(uploadRepository
.checkDuplicateImage(it.getLocalUriPath().getPath())
.subscribeOn(ioThreadScheduler)
.subscribe(imageCheckResult -> {
if (imageCheckResult == IMAGE_OK) {
it.setState(Contribution.STATE_QUEUED);
compositeDisposable.add(contributionsRepository
.save(it)
.subscribeOn(ioThreadScheduler)
.doOnComplete(() -> {
restartUploads(contributionList, index + 1, context);
})
.subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest(
context, ExistingWorkPolicy.KEEP)));
} else {
Timber.e("Contribution already exists");
compositeDisposable.add(contributionsRepository
.deleteContributionFromDB(it)
.subscribeOn(ioThreadScheduler).doOnComplete(() -> {
restartUploads(contributionList, index + 1, context);
})
.subscribe());
}
}, throwable -> {
Timber.e(throwable);
restartUploads(contributionList, index + 1, context);
}));
} else {
it.setState(Contribution.STATE_QUEUED);
compositeDisposable.add(contributionsRepository
.save(it)
.subscribeOn(ioThreadScheduler)
.doOnComplete(() -> {
restartUploads(contributionList, index + 1, context);
})
.subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest(
context, ExistingWorkPolicy.KEEP)));
}
}
/**
* Restarts the upload for the specified list of contributions for the given index.
*
* @param contributionList The list of contributions.
* @param index The index in the list which to be restarted.
* @param context The context in which the operation is being performed.
*/
public void restartUpload(List<Contribution> contributionList, int index, Context context) {
CommonsApplication.isPaused = false;
if (index >= contributionList.size()) {
return;
}
Contribution it = contributionList.get(index);
if (it.getState() == Contribution.STATE_FAILED) {
it.setDateUploadStarted(Calendar.getInstance().getTime());
if (it.getErrorInfo() == null) {
it.setChunkInfo(null);
it.setTransferred(0);
}
compositeDisposable.add(uploadRepository
.checkDuplicateImage(it.getLocalUriPath().getPath())
.subscribeOn(ioThreadScheduler)
.subscribe(imageCheckResult -> {
if (imageCheckResult == IMAGE_OK) {
it.setState(Contribution.STATE_QUEUED);
compositeDisposable.add(contributionsRepository
.save(it)
.subscribeOn(ioThreadScheduler)
.subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest(
context, ExistingWorkPolicy.KEEP)));
} else {
Timber.e("Contribution already exists");
compositeDisposable.add(contributionsRepository
.deleteContributionFromDB(it)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
}));
} else {
it.setState(Contribution.STATE_QUEUED);
compositeDisposable.add(contributionsRepository
.save(it)
.subscribeOn(ioThreadScheduler)
.subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest(
context, ExistingWorkPolicy.KEEP)));
}
}
}

View file

@ -0,0 +1,256 @@
package fr.free.nrw.commons.upload
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import androidx.work.ExistingWorkPolicy
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_FAILED
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_IN_PROGRESS
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_PAUSED
import fr.free.nrw.commons.contributions.Contribution.Companion.STATE_QUEUED
import fr.free.nrw.commons.contributions.ContributionBoundaryCallback
import fr.free.nrw.commons.contributions.ContributionsRemoteDataSource
import fr.free.nrw.commons.contributions.ContributionsRepository
import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK
import io.reactivex.Scheduler
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
import java.util.Calendar
import javax.inject.Inject
import javax.inject.Named
/**
* The presenter class for PendingUploadsFragment and FailedUploadsFragment
*/
class PendingUploadsPresenter @Inject internal constructor(
private val contributionBoundaryCallback: ContributionBoundaryCallback,
private val contributionsRemoteDataSource: ContributionsRemoteDataSource,
private val contributionsRepository: ContributionsRepository,
private val uploadRepository: UploadRepository,
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioThreadScheduler: Scheduler
) : PendingUploadsContract.UserActionListener {
private val compositeDisposable = CompositeDisposable()
lateinit var totalContributionList: LiveData<PagedList<Contribution>>
lateinit var failedContributionList: LiveData<PagedList<Contribution>>
/**
* Setups the paged list of Pending Uploads. This method sets the configuration for paged list
* and ties it up with the live data object. This method can be tweaked to update the lazy
* loading behavior of the contributions list
*/
fun setup() {
val pagedListConfig = PagedList.Config.Builder()
.setPrefetchDistance(50)
.setPageSize(10).build()
val factory = contributionsRepository.fetchContributionsWithStatesSortedByDateUploadStarted(
listOf(STATE_QUEUED, STATE_IN_PROGRESS, STATE_PAUSED)
)
totalContributionList = LivePagedListBuilder(factory, pagedListConfig).build()
}
/**
* Setups the paged list of Failed Uploads. This method sets the configuration for paged list
* and ties it up with the live data object. This method can be tweaked to update the lazy
* loading behavior of the contributions list
*/
fun getFailedContributions() {
val pagedListConfig = PagedList.Config.Builder()
.setPrefetchDistance(50)
.setPageSize(10).build()
val factory = contributionsRepository.fetchContributionsWithStatesSortedByDateUploadStarted(
listOf(STATE_FAILED)
)
failedContributionList = LivePagedListBuilder(factory, pagedListConfig).build()
}
override fun onAttachView(view: PendingUploadsContract.View) {
}
override fun onDetachView() {
compositeDisposable.clear()
contributionsRemoteDataSource.dispose()
contributionBoundaryCallback.dispose()
}
/**
* Deletes the specified upload (contribution) from the database.
*
* @param contribution The contribution object representing the upload to be deleted.
* @param context The context in which the operation is being performed.
*/
override fun deleteUpload(contribution: Contribution?, context: Context?) {
compositeDisposable.add(
contributionsRepository
.deleteContributionFromDB(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe()
)
}
/**
* Pauses all the uploads by changing the state of contributions from STATE_QUEUED and
* STATE_IN_PROGRESS to STATE_PAUSED in the database.
*/
fun pauseUploads() {
CommonsApplication.isPaused = true
compositeDisposable.add(
contributionsRepository
.updateContributionsWithStates(
listOf(STATE_QUEUED, STATE_IN_PROGRESS),
STATE_PAUSED
)
.subscribeOn(ioThreadScheduler)
.subscribe()
)
}
/**
* Deletes contributions from the database that match the specified states.
*
* @param states A list of integers representing the states of the contributions to be deleted.
*/
fun deleteUploads(states: List<Int>) {
compositeDisposable.add(
contributionsRepository
.deleteContributionsFromDBWithStates(states)
.subscribeOn(ioThreadScheduler)
.subscribe()
)
}
/**
* Restarts the uploads for the specified list of contributions starting from the given index.
*
* @param contributionList The list of contributions to be restarted.
* @param index The starting index in the list from which to restart uploads.
* @param context The context in which the operation is being performed.
*/
fun restartUploads(contributionList: List<Contribution>, index: Int, context: Context) {
CommonsApplication.isPaused = false
if (index >= contributionList.size) {
return
}
val contribution = contributionList[index]
if (contribution.state == STATE_FAILED) {
contribution.dateUploadStarted = Calendar.getInstance().time
if (contribution.errorInfo == null) {
contribution.chunkInfo = null
contribution.transferred = 0
}
compositeDisposable.add(
uploadRepository
.checkDuplicateImage(contribution.localUriPath!!.path)
.subscribeOn(ioThreadScheduler)
.subscribe({ imageCheckResult: Int ->
if (imageCheckResult == IMAGE_OK) {
contribution.state = STATE_QUEUED
compositeDisposable.add(
contributionsRepository
.save(contribution)
.subscribeOn(ioThreadScheduler)
.doOnComplete {
restartUploads(contributionList, index + 1, context)
}
.subscribe {
makeOneTimeWorkRequest(
context, ExistingWorkPolicy.KEEP
)
})
} else {
Timber.e("Contribution already exists")
compositeDisposable.add(
contributionsRepository
.deleteContributionFromDB(contribution)
.subscribeOn(ioThreadScheduler).doOnComplete {
restartUploads(contributionList, index + 1, context)
}
.subscribe())
}
}, { throwable: Throwable? ->
Timber.e(throwable)
restartUploads(contributionList, index + 1, context)
})
)
} else {
contribution.state = STATE_QUEUED
compositeDisposable.add(
contributionsRepository
.save(contribution)
.subscribeOn(ioThreadScheduler)
.doOnComplete {
restartUploads(contributionList, index + 1, context)
}
.subscribe {
makeOneTimeWorkRequest(context, ExistingWorkPolicy.KEEP)
}
)
}
}
/**
* Restarts the upload for the specified list of contributions for the given index.
*
* @param contributionList The list of contributions.
* @param index The index in the list which to be restarted.
* @param context The context in which the operation is being performed.
*/
fun restartUpload(contributionList: List<Contribution>, index: Int, context: Context) {
CommonsApplication.isPaused = false
if (index >= contributionList.size) {
return
}
val contribution = contributionList[index]
if (contribution.state == STATE_FAILED) {
contribution.dateUploadStarted = Calendar.getInstance().time
if (contribution.errorInfo == null) {
contribution.chunkInfo = null
contribution.transferred = 0
}
compositeDisposable.add(
uploadRepository
.checkDuplicateImage(contribution.localUriPath!!.path)
.subscribeOn(ioThreadScheduler)
.subscribe { imageCheckResult: Int ->
if (imageCheckResult == IMAGE_OK) {
contribution.state = STATE_QUEUED
compositeDisposable.add(
contributionsRepository
.save(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe {
makeOneTimeWorkRequest(context, ExistingWorkPolicy.KEEP)
}
)
} else {
Timber.e("Contribution already exists")
compositeDisposable.add(
contributionsRepository
.deleteContributionFromDB(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe()
)
}
})
} else {
contribution.state = STATE_QUEUED
compositeDisposable.add(
contributionsRepository
.save(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe {
makeOneTimeWorkRequest(context, ExistingWorkPolicy.KEEP)
}
)
}
}
}