<commit_before>package de.christinecoenen.code.zapp.app.mediathek.controller.downloads;

import android.content.Context;
import android.net.ConnectivityManager;
import android.os.Build;

import androidx.annotation.NonNull;

import com.tonyodev.fetch2.Download;
import com.tonyodev.fetch2.Error;
import com.tonyodev.fetch2.Fetch;
import com.tonyodev.fetch2.FetchConfiguration;
import com.tonyodev.fetch2.FetchListener;
import com.tonyodev.fetch2.NetworkType;
import com.tonyodev.fetch2.Request;
import com.tonyodev.fetch2.Status;
import com.tonyodev.fetch2core.DownloadBlock;

import org.joda.time.DateTime;

import java.util.List;
import java.util.concurrent.TimeUnit;

import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.DownloadException;
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.NoNetworkException;
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.WrongNetworkConditionException;
import de.christinecoenen.code.zapp.app.mediathek.model.DownloadStatus;
import de.christinecoenen.code.zapp.app.mediathek.model.PersistedMediathekShow;
import de.christinecoenen.code.zapp.app.mediathek.model.Quality;
import de.christinecoenen.code.zapp.app.mediathek.repository.MediathekRepository;
import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository;
import io.reactivex.Completable;
import io.reactivex.Flowable;

public class DownloadController implements FetchListener {

	private final Fetch fetch;

	private final ConnectivityManager connectivityManager;
	private final SettingsRepository settingsRepository;
	private final DownloadFileInfoManager downloadFileInfoManager;
	private final MediathekRepository mediathekRepository;

	public DownloadController(Context applicationContext, MediathekRepository mediathekRepository) {
		this.mediathekRepository = mediathekRepository;

		settingsRepository = new SettingsRepository(applicationContext);
		downloadFileInfoManager = new DownloadFileInfoManager(applicationContext, settingsRepository);

		connectivityManager = (ConnectivityManager) applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);

		FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(applicationContext)
			.setNotificationManager(new ZappNotificationManager(applicationContext, mediathekRepository) {
				@NonNull
				@Override
				public Fetch getFetchInstanceForNamespace(@NonNull String namespace) {
					return fetch;
				}
			})
			.enableRetryOnNetworkGain(false)
			.setAutoRetryMaxAttempts(0)
			.build();

		fetch = Fetch.Impl.getInstance(fetchConfiguration);
		fetch.addListener(this);
	}

	public Completable startDownload(PersistedMediathekShow show, Quality quality) {
		return deleteDownloadAsync(show.getId())
			.andThen(Completable.fromAction(() -> startDownloadInternal(show, quality)));
	}

	public void stopDownload(int id) {
		fetch.getDownloadsByRequestIdentifier(id, downloadList -> {
			for (Download download : downloadList) {
				fetch.cancel(download.getId());
			}
		});
	}

	public void deleteDownload(int id) {
		boolean success = deleteDownloadAsync(id).blockingAwait(500, TimeUnit.MILLISECONDS);
		if (!success) {
			throw new RuntimeException("deleteDownloadAsync timed out");
		}
	}

	public Flowable<DownloadStatus> getDownloadStatus(String apiId) {
		return mediathekRepository.getDownloadStatus(apiId);
	}

	public Flowable<Integer> getDownloadProgress(String apiId) {
		return mediathekRepository.getDownloadProgress(apiId);
	}

	public void deleteDownloadsWithDeletedFiles() {
		fetch.getDownloadsWithStatus(Status.COMPLETED, downloads -> {
			for (Download download : downloads) {
				if (downloadFileInfoManager.shouldDeleteDownload(download)) {
					fetch.remove(download.getId());
				}
			}
		});
	}

	private Completable deleteDownloadAsync(int id) {
		return Completable.create(emitter -> fetch.getDownloadsByRequestIdentifier(id, downloadList -> {
			try {
				for (Download download : downloadList) {
					fetch.delete(download.getId());
				}
				emitter.onComplete();
			} catch (Exception e) {
				emitter.onError(e);
			}
		}));
	}

	private void startDownloadInternal(PersistedMediathekShow show, Quality quality) {
		String downloadUrl = show.getMediathekShow().getVideoUrl(quality);
		String filePath = downloadFileInfoManager.getDownloadFilePath(show.getMediathekShow(), quality);

		Request request;
		try {
			request = new Request(downloadUrl, filePath);
			request.setIdentifier(show.getId());
		} catch (Exception e) {
			throw new DownloadException("Constructing download request failed.", e);
		}

		show.setDownloadId(request.getId());
		show.setDownloadedAt(DateTime.now());
		mediathekRepository.updateShow(show);

		enqueueDownload(request);
	}<commit_msg>Retry downloads instead of recreating them whenever possible<commit_after>package de.christinecoenen.code.zapp.app.mediathek.controller.downloads;

import android.content.Context;
import android.net.ConnectivityManager;
import android.os.Build;

import androidx.annotation.NonNull;

import com.tonyodev.fetch2.Download;
import com.tonyodev.fetch2.Error;
import com.tonyodev.fetch2.Fetch;
import com.tonyodev.fetch2.FetchConfiguration;
import com.tonyodev.fetch2.FetchListener;
import com.tonyodev.fetch2.NetworkType;
import com.tonyodev.fetch2.Request;
import com.tonyodev.fetch2.Status;
import com.tonyodev.fetch2.database.DownloadInfo;
import com.tonyodev.fetch2core.DownloadBlock;

import org.joda.time.DateTime;

import java.util.List;

import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.DownloadException;
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.NoNetworkException;
import de.christinecoenen.code.zapp.app.mediathek.controller.downloads.exceptions.WrongNetworkConditionException;
import de.christinecoenen.code.zapp.app.mediathek.model.DownloadStatus;
import de.christinecoenen.code.zapp.app.mediathek.model.PersistedMediathekShow;
import de.christinecoenen.code.zapp.app.mediathek.model.Quality;
import de.christinecoenen.code.zapp.app.mediathek.repository.MediathekRepository;
import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Single;

public class DownloadController implements FetchListener {

	private final Fetch fetch;

	private final ConnectivityManager connectivityManager;
	private final SettingsRepository settingsRepository;
	private final DownloadFileInfoManager downloadFileInfoManager;
	private final MediathekRepository mediathekRepository;

	public DownloadController(Context applicationContext, MediathekRepository mediathekRepository) {
		this.mediathekRepository = mediathekRepository;

		settingsRepository = new SettingsRepository(applicationContext);
		downloadFileInfoManager = new DownloadFileInfoManager(applicationContext, settingsRepository);

		connectivityManager = (ConnectivityManager) applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);

		FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(applicationContext)
			.setNotificationManager(new ZappNotificationManager(applicationContext, mediathekRepository) {
				@NonNull
				@Override
				public Fetch getFetchInstanceForNamespace(@NonNull String namespace) {
					return fetch;
				}
			})
			.enableRetryOnNetworkGain(false)
			.setAutoRetryMaxAttempts(0)
			.build();

		fetch = Fetch.Impl.getInstance(fetchConfiguration);
		fetch.addListener(this);
	}

	public Completable startDownload(PersistedMediathekShow show, Quality quality) {

		String downloadUrl = show.getMediathekShow().getVideoUrl(quality);

		return getDownload(show.getDownloadId())
			.flatMapCompletable(download -> {
				if (download.getId() != 0 && download.getUrl().equals(downloadUrl)) {
					// same quality --> retry
					fetch.retry(show.getDownloadId());
				} else {
					// delete old file with wrong quality
					fetch.delete(show.getDownloadId());

					String filePath = downloadFileInfoManager.getDownloadFilePath(show.getMediathekShow(), quality);

					Request request;
					try {
						request = new Request(downloadUrl, filePath);
						request.setIdentifier(show.getId());
					} catch (Exception e) {
						throw new DownloadException("Constructing download request failed.", e);
					}

					show.setDownloadId(request.getId());
					show.setDownloadedAt(DateTime.now());
					show.setDownloadProgress(0);
					mediathekRepository.updateShow(show);

					enqueueDownload(request);
				}

				return Completable.complete();
			});
	}

	public void stopDownload(int id) {
		fetch.getDownloadsByRequestIdentifier(id, downloadList -> {
			for (Download download : downloadList) {
				fetch.cancel(download.getId());
			}
		});
	}

	public void deleteDownload(int id) {
		fetch.getDownloadsByRequestIdentifier(id, downloadList -> {
			for (Download download : downloadList) {
				fetch.delete(download.getId());
			}
		});
	}

	public Flowable<DownloadStatus> getDownloadStatus(String apiId) {
		return mediathekRepository.getDownloadStatus(apiId);
	}

	public Flowable<Integer> getDownloadProgress(String apiId) {
		return mediathekRepository.getDownloadProgress(apiId);
	}

	public void deleteDownloadsWithDeletedFiles() {
		fetch.getDownloadsWithStatus(Status.COMPLETED, downloads -> {
			for (Download download : downloads) {
				if (downloadFileInfoManager.shouldDeleteDownload(download)) {
					fetch.remove(download.getId());
				}
			}
		});
	}

	/**
	 * @return download with the given id or empty download with id of 0
	 */
	private Single<Download> getDownload(int downloadId) {
		return Single.create(emitter -> fetch.getDownload(downloadId, download -> {
			if (download == null) {
				emitter.onSuccess(new DownloadInfo());
			} else {
				emitter.onSuccess(download);
			}
		}));
	}
{'fraction_non_alphanumeric': 0.09650010115314586, 'fraction_numerical': 0.002731134938296581, 'mean_word_length': 5.6266756032171585, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 134, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3639483', 'n_tokens_mistral': 3224, 'n_tokens_neox': 2859, 'n_words': 491}