From a8740c4df88bd3b72e75aecd64e9366a2cd18207 Mon Sep 17 00:00:00 2001 From: Paul Hawke Date: Wed, 11 Dec 2024 09:52:24 -0600 Subject: [PATCH] Convert PendingUploadsPresenter and contract to Kotlin with some code-cleanup --- .../commons/upload/FailedUploadsFragment.kt | 77 +++-- .../upload/PendingUploadsContract.java | 31 --- .../commons/upload/PendingUploadsContract.kt | 26 ++ .../commons/upload/PendingUploadsFragment.kt | 90 +++--- .../upload/PendingUploadsPresenter.java | 263 ------------------ .../commons/upload/PendingUploadsPresenter.kt | 256 +++++++++++++++++ 6 files changed, 354 insertions(+), 389 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.java create mode 100644 app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.java create mode 100644 app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.kt diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt index dbbab7359..6e0712ea9 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt @@ -10,6 +10,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import fr.free.nrw.commons.R import fr.free.nrw.commons.auth.SessionManager 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.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.media.MediaClient @@ -43,7 +44,7 @@ class FailedUploadsFragment : private lateinit var adapter: FailedUploadsAdapter - var contributionsList = ArrayList() + var contributionsList = mutableListOf() private lateinit var uploadProgressActivity: UploadProgressActivity @@ -71,7 +72,7 @@ class FailedUploadsFragment : inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { binding = FragmentFailedUploadsBinding.inflate(layoutInflater) pendingUploadsPresenter.onAttachView(this) initAdapter() @@ -99,9 +100,9 @@ class FailedUploadsFragment : pendingUploadsPresenter.getFailedContributions() pendingUploadsPresenter.failedContributionList.observe( viewLifecycleOwner, - ) { list: PagedList -> + ) { list: PagedList -> adapter.submitList(list) - contributionsList = ArrayList() + contributionsList = mutableListOf() list.forEach { if (it != null) { contributionsList.add(it) @@ -124,26 +125,22 @@ class FailedUploadsFragment : * Restarts all the failed uploads. */ fun restartUploads() { - if (contributionsList != null) { - pendingUploadsPresenter.restartUploads( - contributionsList, - 0, - this.requireContext().applicationContext, - ) - } + pendingUploadsPresenter.restartUploads( + contributionsList, + 0, + requireContext().applicationContext, + ) } /** * Restarts a specific upload. */ override fun restartUpload(index: Int) { - if (contributionsList != null) { - pendingUploadsPresenter.restartUpload( - contributionsList, - index, - this.requireContext().applicationContext, - ) - } + pendingUploadsPresenter.restartUpload( + contributionsList, + index, + requireContext().applicationContext, + ) } /** @@ -166,7 +163,7 @@ class FailedUploadsFragment : ViewUtil.showShortToast(context, R.string.cancelling_upload) pendingUploadsPresenter.deleteUpload( 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. */ fun deleteUploads() { - if (contributionsList != null) { - DialogUtil.showAlertDialog( - requireActivity(), - String.format( - Locale.getDefault(), - requireActivity().getString(R.string.cancelling_all_the_uploads), - ), - 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) - uploadProgressActivity.hidePendingIcons() - pendingUploadsPresenter.deleteUploads( - listOf(Contribution.STATE_FAILED), - ) - }, - {}, - ) - } + DialogUtil.showAlertDialog( + requireActivity(), + String.format( + Locale.getDefault(), + requireActivity().getString(R.string.cancelling_all_the_uploads), + ), + 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) + uploadProgressActivity.hidePendingIcons() + pendingUploadsPresenter.deleteUploads(listOf(STATE_FAILED)) + }, + {}, + ) } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.java b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.java deleted file mode 100644 index 8b86ecbd2..000000000 --- a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.java +++ /dev/null @@ -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 { - - /** - * Deletes a upload. - */ - void deleteUpload(Contribution contribution, Context context); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.kt b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.kt new file mode 100644 index 000000000..6c18ba622 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsContract.kt @@ -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 { + /** + * Deletes a upload. + */ + fun deleteUpload(contribution: Contribution?, context: Context?) + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt index 4442a64ea..45e9d90a3 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt @@ -10,6 +10,9 @@ import androidx.recyclerview.widget.LinearLayoutManager import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.R 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.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog @@ -35,7 +38,8 @@ class PendingUploadsFragment : private lateinit var adapter: PendingUploadsAdapter private var contributionsSize = 0 - var contributionsList = ArrayList() + + private var contributionsList = mutableListOf() override fun onAttach(context: Context) { super.onAttach(context) @@ -48,7 +52,7 @@ class PendingUploadsFragment : inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { super.onCreate(savedInstanceState) binding = FragmentPendingUploadsBinding.inflate(inflater, container, false) pendingUploadsPresenter.onAttachView(this) @@ -71,27 +75,24 @@ class PendingUploadsFragment : /** * Initializes the recycler view. */ - fun initRecyclerView() { + private fun initRecyclerView() { binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) binding.pendingUploadsRecyclerView.adapter = adapter pendingUploadsPresenter.setup() - pendingUploadsPresenter.totalContributionList.observe( - viewLifecycleOwner, - ) { list: PagedList -> + pendingUploadsPresenter.totalContributionList + .observe(viewLifecycleOwner) { list: PagedList -> contributionsSize = list.size - contributionsList = ArrayList() + contributionsList = mutableListOf() var pausedOrQueuedUploads = 0 list.forEach { if (it != null) { - if (it.state == Contribution.STATE_PAUSED || - it.state == Contribution.STATE_QUEUED || - it.state == Contribution.STATE_IN_PROGRESS + if (it.state == STATE_PAUSED || + it.state == STATE_QUEUED || + it.state == STATE_IN_PROGRESS ) { contributionsList.add(it) } - if (it.state == Contribution.STATE_PAUSED || - it.state == Contribution.STATE_QUEUED - ) { + if (it.state == STATE_PAUSED || it.state == STATE_QUEUED) { pausedOrQueuedUploads++ } } @@ -104,7 +105,7 @@ class PendingUploadsFragment : binding.nopendingTextView.visibility = View.GONE binding.pendingUplaodsLl.visibility = View.VISIBLE adapter.submitList(list) - binding.progressTextView.setText(contributionsSize.toString() + " uploads left") + binding.progressTextView.setText("$contributionsSize uploads left") if ((pausedOrQueuedUploads == contributionsSize) || CommonsApplication.isPaused) { uploadProgressActivity.setPausedIcon(true) } else { @@ -118,23 +119,18 @@ class PendingUploadsFragment : * Cancels a specific upload after getting a confirmation from the user using Dialog. */ override fun deleteUpload(contribution: Contribution?) { + val activity = requireActivity() + val locale = Locale.getDefault() showAlertDialog( - requireActivity(), - String.format( - Locale.getDefault(), - requireActivity().getString(R.string.cancelling_upload), - ), - 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)), + activity, + String.format(locale, activity.getString(R.string.cancelling_upload)), + String.format(locale, activity.getString(R.string.cancel_upload_dialog)), + String.format(locale, activity.getString(R.string.yes)), + String.format(locale, activity.getString(R.string.no)), { ViewUtil.showShortToast(context, R.string.cancelling_upload) pendingUploadsPresenter.deleteUpload( - contribution, - this.requireContext().applicationContext, + contribution, requireContext().applicationContext, ) }, {}, @@ -144,47 +140,35 @@ class PendingUploadsFragment : /** * Restarts all the paused uploads. */ - fun restartUploads() { - if (contributionsList != null) { - pendingUploadsPresenter.restartUploads( - contributionsList, - 0, - this.requireContext().applicationContext, - ) - } - } + fun restartUploads() = pendingUploadsPresenter.restartUploads( + contributionsList, 0, requireContext().applicationContext + ) /** * Pauses all the ongoing uploads. */ - fun pauseUploads() { - pendingUploadsPresenter.pauseUploads() - } + fun pauseUploads() = pendingUploadsPresenter.pauseUploads() /** * Cancels all the uploads after getting a confirmation from the user using Dialog. */ fun deleteUploads() { + val activity = requireActivity() + val locale = Locale.getDefault() showAlertDialog( - requireActivity(), - String.format( - Locale.getDefault(), - requireActivity().getString(R.string.cancelling_all_the_uploads), - ), - 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)), + activity, + String.format(locale, activity.getString(R.string.cancelling_all_the_uploads)), + String.format(locale, activity.getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads)), + String.format(locale, activity.getString(R.string.yes)), + String.format(locale, activity.getString(R.string.no)), { ViewUtil.showShortToast(context, R.string.cancelling_upload) uploadProgressActivity.hidePendingIcons() pendingUploadsPresenter.deleteUploads( listOf( - Contribution.STATE_QUEUED, - Contribution.STATE_IN_PROGRESS, - Contribution.STATE_PAUSED, + STATE_QUEUED, + STATE_IN_PROGRESS, + STATE_PAUSED, ), ) }, diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.java deleted file mode 100644 index ecc9c19b5..000000000 --- a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.java +++ /dev/null @@ -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> totalContributionList; - LiveData> 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 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 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 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 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 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))); - } - } - -} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.kt new file mode 100644 index 000000000..324f988d4 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsPresenter.kt @@ -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> + lateinit var failedContributionList: LiveData> + + /** + * 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) { + 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, 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, 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) + } + ) + } + } +}