I am downloading multiple images using RxJava. Beside downloading I also showing progress for each downloaded file. My problem, is that I need to know when file completely downloaded(currently last part of downloaded progress is not emitted). Since I can download multiple files at same time, I need to pass mediaId of download completed file. Or I should be able to receive last download progress part How I can accomplish this?
downloadThread = new Downloader(media.getMediaUrl(), media.getId(), context);
downloadThread.start();
downloadThread.getProgressObservable()
.sample(30, java.util.concurrent.TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<DownloadProgressEvent>() {
#Override
public void call(DownloadProgressEvent event) {
ProgressBar downloadProgress = (ProgressBar) view.findViewById(R.id.download_progress);
if (event.getTotal() > 0) {
int downloadPercent = (int) ((event.getLoaded() * 100l) / event.getTotal());
downloadProgress.setProgress(downloadPercent);
Log.d(TAG, "download progress: " + String.format("%s / %s /percent: %s / mediaId: %s", event.getLoadedBytes(), event.getTotalBytes(), downloadPercent, event.getMediaId()));
}
}
});
}
Code from Downloader class.
public void loadInBackground() {
try {
URL toDownload = new URL(url);
HttpURLConnection urlConnection = (HttpURLConnection) toDownload.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
String mimeType = MimeTypeMap.getFileExtensionFromUrl(url);
File outFile = new File(helper.getTmpFolder() + "/" + helper.generateUniqueName() + "test." + mimeType);
FileOutputStream fileOutput = new FileOutputStream(outFile);
InputStream inputStream = urlConnection.getInputStream();
totalSize = urlConnection.getContentLength();
loadedSize = 0;
byte[] buffer = new byte[1024];
int bufferLength = 0; //used to store a temporary size of the buffer
while (!killed && (bufferLength = inputStream.read(buffer)) > 0) {
while(!running && !killed) {
Thread.sleep(500);
}
if (!killed) {
fileOutput.write(buffer, 0, bufferLength);
loadedSize += bufferLength;
reportProgress();
}
}
fileOutput.close();
if (killed && outFile.exists()) {
outFile.delete();
}
progressSubject.onCompleted();
} catch (MalformedURLException e) {
e.printStackTrace();
progressSubject.onError(e);
} catch (IOException e) {
e.printStackTrace();
progressSubject.onError(e);
} catch (InterruptedException e) {
e.printStackTrace();
progressSubject.onError(e);
}
}
private void reportProgress() {
progressSubject.onNext(new DownloadProgressEvent(loadedSize, totalSize, mediaId));
}
#Override
public void run() {
loadInBackground();
}
public Subject<DownloadProgressEvent, DownloadProgressEvent> getProgressObservable() {
return progressSubject;
}
After learning that I can't pass parameter via onCompleted I solved my problem like this.
downloadThread.getProgressObservable()
.sample(30, java.util.concurrent.TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted(new Action0() {
#Override
public void call() {
downloadProgress.setProgress(100);
putGoodQualityThumbnail(media, view);;
}
})
.subscribe(new Action1<DownloadProgressEvent>() {
#Override
public void call(DownloadProgressEvent event) {
if (event.getTotal() > 0) {
int downloadPercent = (int) ((event.getLoaded() * 100l) / event.getTotal());
downloadProgress.setProgress(downloadPercent);
}
}
});
Related
I am getting a URL in response. I want to download the html of that URL so that user can see it offline also. Its a recyclerView in which each items contain a URL. So when user clicks on the URL of one item it should save it in external disk.
Below is the code:
NewsAdapter:
case R.id.save:
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.nytimes.com/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Log.i("Retrofit build", "initiated");
ApiInterface retrofitInterface = retrofit.create(ApiInterface.class);
final Call< ResponseBody > call = retrofitInterface.downloadFileWithDynamicUrlSync("2017/09/13/us/nursing-home-deaths-florida.html");
Log.i("Retrofit req execute", "initiated");
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
boolean writtenToDisk = false;
try {
writtenToDisk = writeResponseBodyToDisk(call.execute().body());
} catch (IOException e) {
e.printStackTrace();
}
;
Log.d("success", "file download was a success? " + writtenToDisk);
return null;
}
}.execute();
break;
private boolean writeResponseBodyToDisk(ResponseBody body) {
try {
// todo change the file location/name according to your needs
File futureStudioIconFile = new File(Environment.DIRECTORY_DOWNLOADS + File.separator + "Future Studio Icon.png");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d("filedownload", "file download: " + fileSizeDownloaded + " of " + fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
ApiInterface:
// option 2: using a dynamic URL
#Streaming
#GET
Call<ResponseBody> downloadFileWithDynamicUrlSync(#Url String fileUrl);
I am also getting the error:
Failed to invoke public com.squareup.okhttp.ResponseBody() with no args
Can someone tell me how to implement it correctly.
Use URL with domain name to download file.Remove streaming annotation don't need that.
You are not receiving file body as you are not using complete URL.
Create an interface like this
#GET
Call<ResponseBody> downloadFile(#Url String url);
Then use this code :
public void onResponse(Call<ResponseBody> call, Response<ResponseBody>
response)
{
if (response.isSuccessful()) {
String filePath = Utils.downloadFile(response.body(),"filename");
}
}
public String downloadFile(ResponseBody body, String fileName) {
String filePath = null;
try {
int count;
byte data[] = new byte[1024 * 4];
InputStream bis = new BufferedInputStream(body.byteStream(), 1024 * 8);
File storageDir = new File(Environment.getExternalStorageDirectory(), "AppName");
if (!storageDir.exists()) {
storageDir.mkdirs();
}
File outputFile = new File(storageDir, fileName);
OutputStream output = new FileOutputStream(outputFile);
while ((count = bis.read(data)) != -1) {
output.write(data, 0, count);
}
output.flush();
output.close();
bis.close();
filePath = outputFile.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
}
return filePath;
}
I am using a service in my app that I used to retrofit to download some "apk" files in the background.
I want to check downloaded file size with received file size to make sure I get "apk" completely.
but when i use response.body().contentLength() it is -1!
This is my code:
private void downloadAppFromServer(String url, final String fileId) {
APIService downloadService = ServiceGenerator.createServiceFile(APIService.class, "181412655", "181412655");
Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(url);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, final Response<ResponseBody> response) {
if (response.isSuccess()) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
Log.d("LOGO", "server contacted and has file");
Log.d("LOGO", "FILE SIZE: " + response.body().contentLength());
boolean writtenToDisk = writeResponseBodyToDisk(response.body(), fileId);
Log.d("LOGO", "file download was a success? " + writtenToDisk);
return null;
}
}.execute();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
});
}
And this is my writeToDisk Method:
private boolean writeResponseBodyToDisk(ResponseBody body, String fileId) {
try {
// Location to save downloaded file and filename
File DownloadFile = new File(G.DIR_APK + "/" + fileId + ".apk");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
Log.d("LOGO", "file size is: " + fileSize ); //This is -1 !
outputStream = new FileOutputStream(DownloadFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
}
outputStream.flush();
if (fileSize == fileSizeDownloaded) {
return true;
} else {
return false;
}
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
-1 means that web server doesnt give you any information about length of file.
Retrofit gets header content-length. If it doesnt exist response.body().contentLength() returns -1.
Try to make HEAD HTTP request to the same URL, it might return the Content-Length.
I try to download file from server and save it in internel storage and then install it.
I downloaded it using retrofit by this code
Call<ResponseBody> call = apiService.getNewVersion();
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call,
Response<ResponseBody> response) {
if (response.isSuccessful() ) {
Log.d("1", "server contacted and has file");
boolean writtenToDisk =
writeResponseBodyToDisk(response.body());
Log.d("2", "file download was a success? " + writtenToDisk);
} else {
Log.d("3", "server contact failed");
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("4", "error");
}
});
and I saved it by this code
private boolean writeResponseBodyToDisk(ResponseBody body) {
try {
// todo change the file location/name according to your needs
File futureStudioIconFile = new
File(context.getExternalFilesDir(null) + File.separator + "app-debug.apk");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d("TAG", "file download: " + fileSizeDownloaded + " of "
+ fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
its original 4.56MB but it shows 6.08MB in Android storage.
When I open it to install it shows
version N /A size N/ A
and when I try to install it, it shows this parse problem
Do you have any idea to solve this problem?
How to remove extra bytes?
You should make sure that you use HttpResponseMessage.content in your web service method because a normal return will not be a standard size for the file that you want:
[HttpGet]
[Route("api/Depository/DownloadFileFTP")] // /{pass}
public HttpResponseMessage DownloadFileFTP() //string pass
{
HttpResponseMessage result = null;
string ftphost = "*******";
string ftpfilepath = "/app-debug.apk";
byte[] fileData;
string ftpfullpath = "ftp://" + ftphost + ftpfilepath;
using (WebClient request = new WebClient())
{
request.Credentials = new NetworkCredential("**", "****");
fileData = request.DownloadData(ftpfullpath);
}
byte[] bytes = fileData;
result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new ByteArrayContent(bytes);
result.Content.Headers.ContentDisposition = new
System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "app.apk";
return result;
}
I create a file download from web
This code works but after i go to home page , how can access to thread and this listener ?
filedownloader.java
public class FileDownloader {
public static void download(final String downloadPath, final String filepath, final OnProgressDownloadListener listener) {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try {
URL url = new URL(downloadPath);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
//connection.setDoOutput(true);
connection.connect();
int fileSize = connection.getContentLength();
File file = new File(filepath);
if (file.exists()) {
file.delete();
}
FileOutputStream outputStream = new FileOutputStream(filepath);
InputStream inputStream = connection.getInputStream();
byte[] buffer = new byte[G.DOWNLOAD_BUFFER_SIZE];
int len = 0;
int downloadedSize = 0;
while ((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
downloadedSize += len;
final float downloadPercent = 100.0f * (float) downloadedSize / fileSize;
if (listener != null) {
G.HANDLER.post(new Runnable() {
#Override
public void run() {
listener.onProgressDownload((int) downloadPercent);
}
});
}
}
outputStream.close();
}
catch (MalformedURLException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
onprogressdownloadlistener.java
public interface OnProgressDownloadListener {
public void onProgressDownload(int percent);
}
mainactivity.java
OnProgressDownloadListener listener = new OnProgressDownloadListener() {
#Override
public void onProgressDownload(final int percent) {
Log.i(percent + "%");
}
};
FileDownloader.download(dlFile, G.DIR_APP + "/" + fileName, listener);
Percent return size of download
After start download and close page and go to new page , i want to go back to download page. how to access this thread and the listener ?
Thanks
-Hey you can check if percent in main activity's listener object gets "100%" value then you can get you desired output.
I am trying to write an application that downloads files in the background. The code crashes when it tries to reenter doInBackground(). This happens when doing is set to false before returning. Code follows -
public class DownloadFile extends AsyncTask<String, Integer, String> {
private boolean doing;
private Activity activity;
private Intent intent;
private File beta;
private File alpha;
public DownloadFile(Activity act, Intent intent) {
this.activity = act;
this.intent = intent;
doing = false;
}
#Override
protected String doInBackground(String... sUrl) {
int fileCount = 0;
if (!download(sUrl[0] + "list.txt",
Environment.getExternalStorageDirectory() + "/alpha/list.txt")){
setDoing(false);
return "Download failed";//list.txt could not be downloaded. return error message.
}
fileCount++;
beta = new File(Environment.getExternalStorageDirectory() + "/beta/");
File betalist = new File(beta + "/list.txt");
alpha = new File(Environment.getExternalStorageDirectory() + "/alpha/");
File alphalist = new File(alpha + "/list.txt");
//verify that the file is changed.
if (alphalist.lastModified() == betalist.lastModified()// these two are
// never equal.
|| alphalist.length() == betalist.length()) { // better to check
// the length of
// the files.
setDoing(false);
return "Nothing to download.";
}
try {
FileReader inAlpha = new FileReader(alphalist);
BufferedReader br = new BufferedReader(inAlpha);
String s;
// read the name of each file in a loop
while ((s = br.readLine()) != null) {
// if(fileExistsInBeta(s)){
// copyFromBetaToAlpha(s);
// continue;
// }
// download the file.
//Url will truncate the trailing / so keep if statement as is.
if (!download(sUrl[0] + s,
Environment.getExternalStorageDirectory() + "/alpha/"
+ s)){
setDoing(false);
return "Failed at " + s;// the given file could not be downloaded. return error.
}
fileCount++;
}
br.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
Log.e("Pankaj", e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e("Pankaj", e.getMessage());
} catch (Exception e) {
Log.e("Pankaj", e.getMessage());
}
Log.d("Pankaj", "Download Done");
activity.overridePendingTransition(0, 0);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
activity.finish();
Log.d("Pankaj", "MainActivity Killed");
// rename alpha to beta
deleteSubFolders(beta.toString());
beta.delete();
alpha.renameTo(beta);
if (!alpha.exists()) {
alpha.mkdir();
}
File upper = new File(alpha + "/upper/");
if (!upper.exists())
upper.mkdirs();
File lower = new File(alpha + "/lower/");
if (!lower.exists())
lower.mkdirs();
// ConfLoader.getInstance().reload();//to refresh the settings
// restart the activity
activity.overridePendingTransition(0, 0);
activity.startActivity(intent);
Log.d("Pankaj", "MainActivity restarted");
// now reset done status so we can start again.
setDoing(false);
return "Download finished.";// return the status for onPostExecute.
}
private void copyFromBetaToAlpha(String fileName) {
File beta=new File(Environment.getExternalStorageDirectory()+"/beta/"+fileName);
File alpha=new File(Environment.getExternalStorageDirectory()+"/alpha/"+fileName);
try {
FileInputStream fis=new FileInputStream(beta);
FileOutputStream fos=new FileOutputStream(alpha);
byte[] buf=new byte[1024];
int len;
while((len=fis.read(buf))>0){
fos.write(buf, 0, len);
}
fis.close();
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
protected void onPostExecute(String result) {
Toast.makeText(activity, result, Toast.LENGTH_LONG).show();
super.onPostExecute(result);
}
public boolean download(String url, String file) {
boolean successful = true;
try {
URL u = new URL(url);
URLConnection conn = u.openConnection();
conn.connect();
int filelen = conn.getContentLength();
File f = new File(file);
// skip download if lengths are same
// because the file has been downed fully.
if (f.exists() && filelen == f.length()) {
return successful;
}
InputStream is = u.openStream();
DataInputStream dis = new DataInputStream(is);
byte[] buffer = new byte[1024];
int length;
FileOutputStream fos = new FileOutputStream(f);
while ((length = dis.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
fos.close();
buffer = null;
dis.close();
} catch (MalformedURLException mue) {
Log.e("SYNC getUpdate", "malformed url error", mue);
successful = false;
} catch (IOException ioe) {
Log.e("SYNC getUpdate", "io error", ioe);
successful = false;
} catch (SecurityException se) {
Log.e("SYNC getUpdate", "security error", se);
successful = false;
}
return successful;
}
private void deleteSubFolders(String uri) {
File currentFolder = new File(uri);
File files[] = currentFolder.listFiles();
if (files == null) {
return;
}
for (File f : files) {
if (f.isDirectory()) {
deleteSubFolders(f.toString());
}
// no else, or you'll never get rid of this folder!
f.delete();
}
}
public static int getFilesCount(File file) {
File[] files = file.listFiles();
int count = 0;
for (File f : files)
if (f.isDirectory())
count += getFilesCount(f);
else
count++;
return count;
}
public boolean isDoing() {
return doing;
}
/**
* #param doing
*/
public void setDoing(boolean doing) {
this.doing = doing;
}
private boolean fileExistsInBeta(final String fileName){
boolean exists=false;
File beta=new File(Environment.getExternalStorageDirectory()+"/beta/"+fileName);
if(beta.exists()){
String[] ext=beta.getName().split(".");
String extName=ext[ext.length-1];
exists=(extName!="txt" && extName!="tmr" && extName!="conf");
}
return exists;
}
in the main activity -
public void run() {
if (!downloadFile.isDoing()) {
downloadFile.execute(ConfLoader.getInstance().getListUrl());
downloadFile.setDoing(true);
}
// change the delay so that it covers the time for download and
// doesn't overlap causing multiple downloads jamming the bandwidth.
h.postDelayed(this, 1000);//check after 60 sec.
}
in the onCreate() -
downloadFile = new DownloadFile(this, getIntent());
h = new Handler();
h.postDelayed(this, 1000);
Any help is appreciated. Thanks in advance.
EDIT:
The logcat error is Cannot execute task the task is already running.
Cannot execute task: Task has already been executed(A task can only be executed once).
EDIT:
Is it possible that the error is because I am trying to execute the asynchtask again in run(). Perhaps AsynchTask does not allow re-entry.
Try using this
1. First create a dialogue
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_DOWNLOAD_PROGRESS:
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("Downloading file..");
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(false);
mProgressDialog.show();
return mProgressDialog;
default:
return null;
}
}
The download async task
class DownloadFileAsync extends AsyncTask<String, String, String> {
#SuppressWarnings("deprecation")
#Override
protected void onPreExecute() {
super.onPreExecute();
showDialog(DIALOG_DOWNLOAD_PROGRESS);
}
#Override
protected String doInBackground(String... aurl) {
int count;
File root = android.os.Environment.getExternalStorageDirectory();
//
File dir = new File (root.getAbsolutePath()+"/Downl");
if(dir.exists()==false) {
dir.mkdirs();
}
File file = new File(dir, url.substring(url.lastIndexOf("/")+1)); //name of file
try {
URL url = new URL(aurl[0]);
URLConnection conexion = url.openConnection();
conexion.connect();
int lenghtOfFile = conexion.getContentLength();
Log.d("ANDRO_ASYNC", "Lenght of file: " + lenghtOfFile);
InputStream input = new BufferedInputStream(url.openStream());
OutputStream output = new FileOutputStream(file);
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1)
{
total += count;
publishProgress(""+(int)((total*100)/lenghtOfFile));
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
} catch (Exception e) {}
return null;
}
protected void onProgressUpdate(String... progress) {
Log.d("ANDRO_ASYNC",progress[0]);
mProgressDialog.setProgress(Integer.parseInt(progress[0]));
}
#SuppressWarnings("deprecation")
#Override
protected void onPostExecute(String unused) {
dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
Toast.makeText(DisplayActivity.this,"Successfully downloaded in phone memory.", Toast.LENGTH_SHORT).show();
}
}
Call the async new DownloadFileAsync().execute(url); //pass ur url