package download_manager.services;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.os.AsyncTask;
import android.webkit.URLUtil;
import application.App;
import data.object_holder.SettingsHolder;
import tools.StorageUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import static java.lang.String.valueOf;
import static tools.NetworkUtils.isNetworkAvailable;
import static tools.StorageUtils.size;
/**
* <p><b>DownloadTask</b> is download file from internet. Its is the main class that responsible for
* downloading file. </p>
*
* @author shibaprasad
* @version 1.2
* @since 8.2.15
*/
public class DownloadTask extends AsyncTask<Void, Integer, Long> {
//default time out.
public final static int TIME_OUT = (1000 * 15);
//download suffix.
public static final String TEMP_SUFFIX = ".download";
//default buffer size.
public static int BUFFER_SIZE = 1024 * 32;
//ui update loop.
private static int UI_UPDATE_LOOP = 0;
//download property.
private String fileName;
private String filePath;
private String fileUrl;
private String fileWebpage;
//download progress value.
private long downloadedFileSize;
private long previousFileSize;
private long totalFileSize;
private long downloadPercentage;
private long networkSpeed;
//download files.
private File tmpDownloadFile;
private File originalDownloadFile;
//download progress listener.
private DownloadTaskListener downloadListener;
//application context.
private Context context;
private App application;
private double startTime;
private double totalTime;
//download error.
private Throwable download_error = null;
//is pause boolean value.
private boolean is_pause = false;
/**
* Private variable for holding the progress related data.
*/
private long startingTime, //start time.
previousDownloadedSize = downloadedFileSize;
/**
* DownloadTask public constructor for creating a new Task reference object.
*
* @param application application
* @param context the service context.
* @param fileWebpage the file webpage
* @param fileUrl the file url.
* @param filePath the file path.
* @param fileName the file name.
* @param taskListener task listener.
* @throws IOException
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
public DownloadTask(App application, Context context, String fileWebpage, String fileUrl,
String filePath, String fileName, DownloadTaskListener taskListener) throws IOException {
super();
this.context = context;
this.application = application;
this.fileUrl = fileUrl;
this.fileName = fileName;
this.filePath = filePath;
this.fileWebpage = fileWebpage;
//download progress sender interface.
this.downloadListener = taskListener;
//download file.
this.originalDownloadFile = new File(filePath, this.fileName);
this.tmpDownloadFile = new File(filePath, this.fileName + TEMP_SUFFIX);
//create a new tmp download file.
this.tmpDownloadFile.createNewFile();
//set the buffer size and the update loop number.
BUFFER_SIZE = 1024 * application.getSettingsHolder().maxSpeed;
UI_UPDATE_LOOP = this.application.getDownloadFunctions().getDownloadUpdateLoop();
if (BUFFER_SIZE == 0 || BUFFER_SIZE < (1024 * 2)) {
BUFFER_SIZE = 1024 * 16;
application.getSettingsHolder().maxSpeed = BUFFER_SIZE;
SettingsHolder.save(application.getSettingsHolder());
}
}
public String getFileUrl() {
return fileUrl;
}
public String getFileName() {
return this.fileName;
}
public String getFilePath() {
return this.filePath;
}
public String getFileWebpage() {
return this.fileWebpage;
}
public boolean isPause() {
return is_pause;
}
public long getDownloadPercent() {
return downloadPercentage;
}
public long getDownloadSize() {
return downloadedFileSize + previousFileSize;
}
public long getTotalSize() {
return totalFileSize;
}
public long getDownloadSpeed() {
return this.networkSpeed;
}
public double getTotalTime() {
return this.totalTime;
}
public DownloadTaskListener getListener() {
return this.downloadListener;
}
/**
* Calculate a file size in Kb-Mb-Gb
*
* @param size the file size.
*/
public String sizeInMB(long size) {
long kb_1 = 1024;
long mb_1 = kb_1 * 1024;
long gb_1 = mb_1 * 1024;
if (size < kb_1) {
return size + " Byte ";
}
if (size > kb_1 && size < mb_1) {
float speed = size / 1024f;
return speed + " Kb ";
}
if (size > mb_1 && size < gb_1) {
float speed = (float) size / (float) mb_1;
return speed + " Mb ";
}
if (size > gb_1) {
float speed = (float) size / (float) gb_1;
return speed + " Gb ";
}
return null;
}
/**
* System call this method when the task is starting to execute in background.
*/
@Override
protected void onPreExecute() {
//save the starting download tine.
startTime = System.currentTimeMillis();
//send message that download task has started.
if (downloadListener != null) {
downloadListener.preDownload(this);
}
}
/**
* System call this method when the task is executed in background.
*
* @param params the parameter.
* @return totalByteRead
*/
@SuppressWarnings("ConstantConditions")
@Override
protected Long doInBackground(Void... params) {
long totalByteRead = -1;
//the file writer class and the input stream.
RandomFileAccess randomFileAccess = null;
InputStream inputStream = null;
//try to download the file from the internet.
try {
randomFileAccess = new RandomFileAccess(this.tmpDownloadFile, "rw");
totalByteRead = downloadEngine(randomFileAccess, inputStream);
} catch (Exception error) {
error.printStackTrace();
//get and save the error.
this.download_error = error;
//send message that download has been paused due to an unexpected error.
downloadListener.errorDownload(this, download_error);
//log to the console.
App.log('e', getClass().getName(), "Download has paused due to unexpected error... \n" +
"Error message : \'" + error.getMessage() + "\'.");
} finally {
//close the file writer.
try {
if (randomFileAccess != null)
randomFileAccess.close();
} catch (IOException ioError) {
ioError.printStackTrace();
}
//close the input stream.
try {
if (inputStream != null)
inputStream.close();
} catch (IOException ioError) {
ioError.printStackTrace();
}
}
return totalByteRead;
}
/**
* System call this method for updating the download progress to the ui.
*
* @param progress the progress
*/
@Override
protected void onProgressUpdate(Integer... progress) {
if (UI_UPDATE_LOOP < 1) {
//total time since last update.
totalTime = System.currentTimeMillis() - startTime;
downloadedFileSize = progress[0];
if (totalFileSize == -1) {
downloadPercentage = 0; //download percent is 0.
} else {
downloadPercentage = (downloadedFileSize + previousFileSize) * 100 / totalFileSize;
}
if (downloadListener != null) {
long time = System.currentTimeMillis() - startingTime;
startingTime = System.currentTimeMillis();
long downloadByte = downloadedFileSize - previousDownloadedSize;
previousDownloadedSize = downloadedFileSize;
long timeInSec = time / 1000; //time in sec.
networkSpeed = downloadByte / (timeInSec < 1 ? 1 : timeInSec);
if (networkSpeed < 2) {
networkSpeed = (long) (downloadedFileSize / (totalTime / 1000));
}
App.log('i', getClass().getName(), "Download time : " + valueOf(time / 1000) + "sec. " +
size(downloadByte));
//send message that the ui need to update.
downloadListener.updateProcess(this);
}
//set the ui update loop.
UI_UPDATE_LOOP = application.getDownloadFunctions().getDownloadUpdateLoop();
} else {
//decrease the ui update loop.
UI_UPDATE_LOOP--;
}
}
/**
* System call this method when the download has stop or finished.
*
* @param result the resulted download byte count.
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
protected void onPostExecute(Long result) {
if (result == -1 || is_pause || download_error != null) {
//send message that download has stopped for an error.
if (downloadListener != null) {
downloadListener.errorDownload(this, download_error);
}
return;
}
//rename the tmp download file to its original name.
tmpDownloadFile.renameTo(originalDownloadFile);
//send message that download has finished.
if (downloadListener != null)
downloadListener.finishDownload(this);
}
/**
* System call this method when used to stop the running download.
*/
@Override
public void onCancelled() {
super.onCancelled();
is_pause = true;
//send message that download has stopped for an error.
downloadListener.errorDownload(this, null);
}
/**
* Download engine to download file from the internet.
*
* @param randomFileAccess the random file access.
* @param inputStream the input stream.
* @return the total downloaded byte count.
* @throws Exception
*/
@SuppressWarnings("ParameterCanBeLocal")
private long downloadEngine(RandomFileAccess randomFileAccess, InputStream inputStream) throws Exception {
long total_byte_read = 0;
if (!URLUtil.isHttpUrl(fileUrl) && !URLUtil.isHttpsUrl(fileUrl))
throw new Exception("Does not support the url protocol.");
if (!URLUtil.isValidUrl(fileUrl))
throw new Exception("Url is not valid.");
if (!isNetworkAvailable(context))
throw new NetworkErrorException("Network is not available.");
URL url = new URL(fileUrl);
URLConnection urlConnection = url.openConnection();
HttpURLConnection httpURLConnection;
if (urlConnection instanceof HttpURLConnection) {
httpURLConnection = (HttpURLConnection) urlConnection;
} else {
throw new Exception("Unsupported protocol.");
}
//set time out.
httpURLConnection.setConnectTimeout(1000 * 30);
httpURLConnection.setReadTimeout(1000 * 20);
//set the get connection method.
httpURLConnection.setRequestMethod("GET");
if (tmpDownloadFile.exists())
previousFileSize = tmpDownloadFile.length();
//set the download rang property.
httpURLConnection.setRequestProperty("Range", "bytes=" + previousFileSize + "-");
//connect the http connection.
httpURLConnection.connect();
if (httpURLConnection.getResponseCode() / 100 != 2) {
//send a error log to console.
App.log('e', getClass().getName(), "HTTP Response Code is bad : " +
httpURLConnection.getResponseMessage() + " " + httpURLConnection.getResponseCode());
throw new Exception("Http response is Bad .");
}
//set the total download size.
totalFileSize = previousFileSize + httpURLConnection.getContentLength();
File originalFile = new File(filePath, fileName);
if (originalFile.exists() && totalFileSize == originalFile.length())
throw new Exception("File already exists. Skipping download.");
long availableStorage = StorageUtils.getAvailableStorage(tmpDownloadFile);
if (totalFileSize - previousFileSize > availableStorage) {
App.log('e', getClass().getName(), "SD Card has no memory. ");
throw new Exception("SD card has no memory.");
}
//update progress.
publishProgress(0);
randomFileAccess.seek(previousFileSize);
inputStream = httpURLConnection.getInputStream();
while (!isPause()) {
byte buffer[];
buffer = new byte[application.getDownloadFunctions().getDownloadBufferSize()];
int read_byte = inputStream.read(buffer);
if (read_byte == -1)
break;
randomFileAccess.write(buffer, 0, read_byte);
total_byte_read += read_byte;
if (!isNetworkAvailable(context)) {
App.log('e', getClass().getName(), "Network is not available.");
throw new NetworkErrorException("Network is blocked.");
}
}
return total_byte_read;
}
/**
* Private class that write byte to the download file.
*/
private final class RandomFileAccess extends RandomAccessFile {
//the total download byte counter.
private int totalDownloadedByte = 0;
/**
* Public constructor.
*
* @param file the file which to be written on.
* @param mode the writing mode.
* @throws FileNotFoundException
*/
public RandomFileAccess(File file, String mode) throws FileNotFoundException {
super(file, mode);
}
/**
* Write byte to the file.
*
* @param buffer the buffer size.
* @param offset the offset.
* @param downloadedByte the byte.
* @throws IOException
*/
@Override
public void write(byte[] buffer, int offset, int downloadedByte) throws IOException {
super.write(buffer, offset, downloadedByte);
//log to console.
App.log('i', getClass().getName(), "Writing.... buffer." + totalDownloadedByte);
totalDownloadedByte += downloadedByte;
publishProgress(totalDownloadedByte);
}
}
}