Thursday, 27 November 2014

How to download files using web cache validation (ETag) - Android

If your app often download files, you want to efficiently keep track of the files you already have so you don't need to download them again.
The HTTP protocol helps us handle this task with much ease by making use of ETags

The class below is ready to use. Copy and paste the code into your Android project.
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;

public class FileDownloadHelper {
	private final int BUFFER_SIZE=1024*32;
	private final String IFNONEMATCH_HEADER_FIELD = "If-None-Match";
	private final String ETAG_HEADER_FIELD = "ETag";
	
	public void downloadToFile(String sourceUrl, String destFile) throws IOException{
		URL url = new URL(sourceUrl);
			
		downloadToFileHTTP(url, destFile);		
	}
		
	private void downloadToFileHTTP(URL url, String destFile) throws IOException{
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		// accept compressed content
		connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
		try{
			writeHTTPDataToFileStream(connection, destFile);
		}finally{
			connection.disconnect();
		}	
	}
	
	private void writeHTTPDataToFileStream(HttpURLConnection connection, String destFile) throws IOException{
		String etagFileName = buildEtagFileName(destFile);
		String etag = readTextFile(etagFileName);
		
		addETagHeader(connection, etag);
		
		int responseCode = connection.getResponseCode(); 		
		if(responseCode == HttpURLConnection.HTTP_OK){
			FileOutputStream destFileStream = new FileOutputStream(destFile);
			try{
				InputStream connectionStream = new BufferedInputStream(connection.getInputStream());
				try{
					int bytesRead;
					byte[] readBuffer = new byte[BUFFER_SIZE];
					while((bytesRead = connectionStream.read(readBuffer)) > 0){
						destFileStream.write(readBuffer, 0, bytesRead);
					}
				}finally{
					connectionStream.close();
				}
			}finally{
				destFileStream.close();
			}
			saveETagHeader(connection, destFile);
		}
		
	}
	
	private void saveETagHeader(HttpURLConnection connection, String destFile) throws IOException {
		String etag = connection.getHeaderField(ETAG_HEADER_FIELD);
		if(etag != null && etag.length()>2){
			String etagFile = buildEtagFileName(destFile);
			saveToTextFile(etagFile, etag);
		}
	}

	private void addETagHeader(HttpURLConnection connection, String etag){
		// greater than 2 because it must at least include the double quotes
		if(etag!=null && etag.length()>2){
			connection.addRequestProperty(IFNONEMATCH_HEADER_FIELD, etag);
		}
	}
	
	private String readTextFile(String fileName) throws IOException{
		File f = new File(fileName);
		if(f.exists() && f.length()>0){
			Reader reader = new BufferedReader(new FileReader(fileName));
			try {
				char[] buffer = new char[1024 * 8];
				StringBuilder resultBuilder = new StringBuilder();
				int br;
				while ((br = reader.read(buffer)) > 0) {
					resultBuilder.append(buffer, 0, br);
				}
				return resultBuilder.toString();
			} finally {
				reader.close();
			}
		}
		return "";
	}
	
	private void saveToTextFile(String fileName, String contents)	throws IOException {
		Writer writer = new BufferedWriter(new FileWriter(fileName));
		try {
			writer.write(contents);
		} finally {
			writer.close();
		}
	}
	
	private String buildEtagFileName(String fileName){
		return fileName + ".etag";
	}	
}


Note that a .etag file will be created in the same directory as the downloaded file and reused in future downloads.
Just call FileDownloadHelper.downloadToFile like this:

FileDownloadHelper client = new FileDownloadHelper(); // assuming you are in the activity context String fileName = getFilesDir().getAbsolutePath()+"/devhelpers.html"; // this will download an html page but you can pass any url to the method String webPageUrl = "http://devhelpers.blogspot.ca/"; client.downloadToFile(webPageUrl, fileName);

No comments:

Post a Comment