Refactored updating code to make future modifications easier.
Rebased several months of work, and attempted to resolve any conflicts. The conflicts were a tad more difficult than usual to resolve because they were in files where large blocks of code were refactored into different files, and git didn't realise. Conflicts: src/org/fdroid/fdroid/RepoXMLHandler.java src/org/fdroid/fdroid/UpdateService.java
This commit is contained in:
		
							parent
							
								
									23dcfadefa
								
							
						
					
					
						commit
						2b1c335ea9
					
				@ -65,7 +65,7 @@ public class DB {
 | 
				
			|||||||
    // Get access to the database. Must be called before any database activity,
 | 
					    // Get access to the database. Must be called before any database activity,
 | 
				
			||||||
    // and releaseDB must be called subsequently. Returns null in the event of
 | 
					    // and releaseDB must be called subsequently. Returns null in the event of
 | 
				
			||||||
    // failure.
 | 
					    // failure.
 | 
				
			||||||
    static DB getDB() {
 | 
					    public static DB getDB() {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            dbSync.acquire();
 | 
					            dbSync.acquire();
 | 
				
			||||||
            return dbInstance;
 | 
					            return dbInstance;
 | 
				
			||||||
@ -75,7 +75,7 @@ public class DB {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Release database access lock acquired via getDB().
 | 
					    // Release database access lock acquired via getDB().
 | 
				
			||||||
    static void releaseDB() {
 | 
					    public static void releaseDB() {
 | 
				
			||||||
        dbSync.release();
 | 
					        dbSync.release();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,8 @@ import java.io.FileInputStream;
 | 
				
			|||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.security.MessageDigest;
 | 
					import java.security.MessageDigest;
 | 
				
			||||||
import java.security.NoSuchAlgorithmException;
 | 
					import java.security.NoSuchAlgorithmException;
 | 
				
			||||||
 | 
					import java.security.cert.Certificate;
 | 
				
			||||||
 | 
					import java.security.cert.CertificateEncodingException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class Hasher {
 | 
					public class Hasher {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -94,6 +96,16 @@ public class Hasher {
 | 
				
			|||||||
        digest.reset();
 | 
					        digest.reset();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static String hex(Certificate cert) {
 | 
				
			||||||
 | 
					        byte[] encoded = null;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            encoded = cert.getEncoded();
 | 
				
			||||||
 | 
					        } catch(CertificateEncodingException e) {
 | 
				
			||||||
 | 
					            encoded = new byte[0];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return hex(encoded);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static String hex(byte[] sig) {
 | 
					    public static String hex(byte[] sig) {
 | 
				
			||||||
        byte[] csig = new byte[sig.length * 2];
 | 
					        byte[] csig = new byte[sig.length * 2];
 | 
				
			||||||
        for (int j = 0; j < sig.length; j++) {
 | 
					        for (int j = 0; j < sig.length; j++) {
 | 
				
			||||||
 | 
				
			|||||||
@ -19,36 +19,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package org.fdroid.fdroid;
 | 
					package org.fdroid.fdroid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.BufferedReader;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import java.io.File;
 | 
					import org.fdroid.fdroid.updater.RepoUpdater;
 | 
				
			||||||
import java.io.FileOutputStream;
 | 
					 | 
				
			||||||
import java.io.FileReader;
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.io.InputStream;
 | 
					 | 
				
			||||||
import java.io.OutputStream;
 | 
					 | 
				
			||||||
import java.net.HttpURLConnection;
 | 
					 | 
				
			||||||
import java.net.MalformedURLException;
 | 
					 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
import java.security.cert.Certificate;
 | 
					 | 
				
			||||||
import java.text.ParseException;
 | 
					 | 
				
			||||||
import java.text.SimpleDateFormat;
 | 
					 | 
				
			||||||
import java.util.Date;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.jar.JarEntry;
 | 
					 | 
				
			||||||
import java.util.jar.JarFile;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import javax.net.ssl.SSLHandshakeException;
 | 
					 | 
				
			||||||
import javax.xml.parsers.SAXParser;
 | 
					 | 
				
			||||||
import javax.xml.parsers.SAXParserFactory;
 | 
					 | 
				
			||||||
import org.xml.sax.Attributes;
 | 
					import org.xml.sax.Attributes;
 | 
				
			||||||
import org.xml.sax.InputSource;
 | 
					 | 
				
			||||||
import org.xml.sax.SAXException;
 | 
					import org.xml.sax.SAXException;
 | 
				
			||||||
import org.xml.sax.XMLReader;
 | 
					 | 
				
			||||||
import org.xml.sax.helpers.DefaultHandler;
 | 
					import org.xml.sax.helpers.DefaultHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.os.Bundle;
 | 
					import java.text.ParseException;
 | 
				
			||||||
import android.content.Context;
 | 
					import java.text.SimpleDateFormat;
 | 
				
			||||||
import android.util.Log;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class RepoXMLHandler extends DefaultHandler {
 | 
					public class RepoXMLHandler extends DefaultHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,14 +57,9 @@ public class RepoXMLHandler extends DefaultHandler {
 | 
				
			|||||||
    private int progressCounter = 0;
 | 
					    private int progressCounter = 0;
 | 
				
			||||||
    private ProgressListener progressListener;
 | 
					    private ProgressListener progressListener;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final int PROGRESS_TYPE_DOWNLOAD     = 1;
 | 
					 | 
				
			||||||
    public static final int PROGRESS_TYPE_PROCESS_XML  = 2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static final String PROGRESS_DATA_REPO = "repo";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The date format used in the repo XML file.
 | 
					    // The date format used in the repo XML file.
 | 
				
			||||||
    private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
 | 
					    private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
 | 
				
			||||||
    private static final SimpleDateFormat logDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private int totalAppCount;
 | 
					    private int totalAppCount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -98,6 +72,22 @@ public class RepoXMLHandler extends DefaultHandler {
 | 
				
			|||||||
        progressListener = listener;
 | 
					        progressListener = listener;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public int getMaxAge() {
 | 
				
			||||||
 | 
					        int age = 0;
 | 
				
			||||||
 | 
					        if (maxage != null) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                age = Integer.parseInt(maxage);
 | 
				
			||||||
 | 
					            } catch (NumberFormatException e) {
 | 
				
			||||||
 | 
					                // Do nothing...
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return age;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String getPubKey() {
 | 
				
			||||||
 | 
					        return pubkey;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void characters(char[] ch, int start, int length) {
 | 
					    public void characters(char[] ch, int start, int length) {
 | 
				
			||||||
        curchars.append(ch, start, length);
 | 
					        curchars.append(ch, start, length);
 | 
				
			||||||
@ -250,12 +240,6 @@ public class RepoXMLHandler extends DefaultHandler {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static Bundle createProgressData(String repoAddress) {
 | 
					 | 
				
			||||||
        Bundle data = new Bundle();
 | 
					 | 
				
			||||||
        data.putString(PROGRESS_DATA_REPO, repoAddress);
 | 
					 | 
				
			||||||
        return data;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void startElement(String uri, String localName, String qName,
 | 
					    public void startElement(String uri, String localName, String qName,
 | 
				
			||||||
            Attributes attributes) throws SAXException {
 | 
					            Attributes attributes) throws SAXException {
 | 
				
			||||||
@ -275,11 +259,11 @@ public class RepoXMLHandler extends DefaultHandler {
 | 
				
			|||||||
            curapp = new DB.App();
 | 
					            curapp = new DB.App();
 | 
				
			||||||
            curapp.detail_Populated = true;
 | 
					            curapp.detail_Populated = true;
 | 
				
			||||||
            curapp.id = attributes.getValue("", "id");
 | 
					            curapp.id = attributes.getValue("", "id");
 | 
				
			||||||
            Bundle progressData = createProgressData(repo.address);
 | 
					            Bundle progressData = RepoUpdater.createProgressData(repo.address);
 | 
				
			||||||
            progressCounter ++;
 | 
					            progressCounter ++;
 | 
				
			||||||
            progressListener.onProgress(
 | 
					            progressListener.onProgress(
 | 
				
			||||||
                new ProgressListener.Event(
 | 
					                new ProgressListener.Event(
 | 
				
			||||||
                    RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML, progressCounter,
 | 
					                    RepoUpdater.PROGRESS_TYPE_PROCESS_XML, progressCounter,
 | 
				
			||||||
                    totalAppCount, progressData));
 | 
					                    totalAppCount, progressData));
 | 
				
			||||||
        } else if (localName.equals("package") && curapp != null && curapk == null) {
 | 
					        } else if (localName.equals("package") && curapp != null && curapk == null) {
 | 
				
			||||||
            curapk = new DB.Apk();
 | 
					            curapk = new DB.Apk();
 | 
				
			||||||
@ -292,228 +276,6 @@ public class RepoXMLHandler extends DefaultHandler {
 | 
				
			|||||||
        curchars.setLength(0);
 | 
					        curchars.setLength(0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Get a remote file. Returns the HTTP response code.
 | 
					 | 
				
			||||||
    // If 'etag' is not null, it's passed to the server as an If-None-Match
 | 
					 | 
				
			||||||
    // header, in which case expect a 304 response if nothing changed.
 | 
					 | 
				
			||||||
    // In the event of a 200 response ONLY, 'retag' (which should be passed
 | 
					 | 
				
			||||||
    // empty) may contain an etag value for the response, or it may be left
 | 
					 | 
				
			||||||
    // empty if none was available.
 | 
					 | 
				
			||||||
    private static int getRemoteFile(Context ctx, String url, String dest,
 | 
					 | 
				
			||||||
            String etag, StringBuilder retag,
 | 
					 | 
				
			||||||
            ProgressListener progressListener,
 | 
					 | 
				
			||||||
            ProgressListener.Event progressEvent) throws MalformedURLException,
 | 
					 | 
				
			||||||
            IOException {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        long startTime = System.currentTimeMillis();
 | 
					 | 
				
			||||||
        URL u = new URL(url);
 | 
					 | 
				
			||||||
        HttpURLConnection connection = (HttpURLConnection) u.openConnection();
 | 
					 | 
				
			||||||
        if (etag != null)
 | 
					 | 
				
			||||||
            connection.setRequestProperty("If-None-Match", etag);
 | 
					 | 
				
			||||||
        int code = connection.getResponseCode();
 | 
					 | 
				
			||||||
        if (code == 200) {
 | 
					 | 
				
			||||||
            // Testing in the emulator for me, showed that figuring out the filesize took about 1 to 1.5 seconds.
 | 
					 | 
				
			||||||
            // To put this in context, downloading a repo of:
 | 
					 | 
				
			||||||
            //  - 400k takes ~6 seconds
 | 
					 | 
				
			||||||
            //  - 5k   takes ~3 seconds
 | 
					 | 
				
			||||||
            // on my connection. I think the 1/1.5 seconds is worth it, because as the repo grows, the tradeoff will
 | 
					 | 
				
			||||||
            // become more worth it.
 | 
					 | 
				
			||||||
            progressEvent.total = connection.getContentLength();
 | 
					 | 
				
			||||||
            Log.d("FDroid", "Downloading " + progressEvent.total + " bytes from " + url);
 | 
					 | 
				
			||||||
            InputStream input = null;
 | 
					 | 
				
			||||||
            OutputStream output = null;
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                input = connection.getInputStream();
 | 
					 | 
				
			||||||
                output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
 | 
					 | 
				
			||||||
                Utils.copy(input, output, progressListener, progressEvent);
 | 
					 | 
				
			||||||
            } finally {
 | 
					 | 
				
			||||||
                Utils.closeQuietly(output);
 | 
					 | 
				
			||||||
                Utils.closeQuietly(input);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            String et = connection.getHeaderField("ETag");
 | 
					 | 
				
			||||||
            if (et != null)
 | 
					 | 
				
			||||||
                retag.append(et);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Log.d("FDroid", "Fetched " + url + " (" + progressEvent.total +
 | 
					 | 
				
			||||||
                " bytes) in " + (System.currentTimeMillis() - startTime) +
 | 
					 | 
				
			||||||
                "ms");
 | 
					 | 
				
			||||||
        return code;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Do an update from the given repo. All applications found, and their
 | 
					 | 
				
			||||||
    // APKs, are added to 'apps'. (If 'apps' already contains an app, its
 | 
					 | 
				
			||||||
    // APKs are merged into the existing one).
 | 
					 | 
				
			||||||
    // Returns null if successful, otherwise an error message to be displayed
 | 
					 | 
				
			||||||
    // to the user (if there is an interactive user!)
 | 
					 | 
				
			||||||
    // 'newetag' should be passed empty. On success, it may contain an etag
 | 
					 | 
				
			||||||
    // value for the index that was successfully processed, or it may contain
 | 
					 | 
				
			||||||
    // null if none was available.
 | 
					 | 
				
			||||||
    public static String doUpdate(Context ctx, DB.Repo repo,
 | 
					 | 
				
			||||||
            List<DB.App> apps, StringBuilder newetag, List<Integer> keeprepos,
 | 
					 | 
				
			||||||
            ProgressListener progressListener) {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            int code = 0;
 | 
					 | 
				
			||||||
            if (repo.pubkey != null) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is a signed repo - we download the jar file,
 | 
					 | 
				
			||||||
                // check the signature, and extract the index...
 | 
					 | 
				
			||||||
                Log.d("FDroid", "Getting signed index from " + repo.address + " at " +
 | 
					 | 
				
			||||||
                    logDateFormat.format(new Date(System.currentTimeMillis())));
 | 
					 | 
				
			||||||
                String address = repo.address + "/index.jar?"
 | 
					 | 
				
			||||||
                    + ctx.getString(R.string.version_name);
 | 
					 | 
				
			||||||
                Bundle progressData = createProgressData(repo.address);
 | 
					 | 
				
			||||||
                ProgressListener.Event event = new ProgressListener.Event(
 | 
					 | 
				
			||||||
                        RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, progressData);
 | 
					 | 
				
			||||||
                code = getRemoteFile(ctx, address, "tempindex.jar",
 | 
					 | 
				
			||||||
                        repo.lastetag, newetag, progressListener, event );
 | 
					 | 
				
			||||||
                if (code == 200) {
 | 
					 | 
				
			||||||
                    String jarpath = ctx.getFilesDir() + "/tempindex.jar";
 | 
					 | 
				
			||||||
                    JarFile jar = null;
 | 
					 | 
				
			||||||
                    JarEntry je;
 | 
					 | 
				
			||||||
                    Certificate[] certs;
 | 
					 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        jar = new JarFile(jarpath, true);
 | 
					 | 
				
			||||||
                        je = (JarEntry) jar.getEntry("index.xml");
 | 
					 | 
				
			||||||
                        File efile = new File(ctx.getFilesDir(),
 | 
					 | 
				
			||||||
                                "/tempindex.xml");
 | 
					 | 
				
			||||||
                        InputStream input = null;
 | 
					 | 
				
			||||||
                        OutputStream output = null;
 | 
					 | 
				
			||||||
                        try {
 | 
					 | 
				
			||||||
                            input = jar.getInputStream(je);
 | 
					 | 
				
			||||||
                            output = new FileOutputStream(efile);
 | 
					 | 
				
			||||||
                            Utils.copy(input, output);
 | 
					 | 
				
			||||||
                        } finally {
 | 
					 | 
				
			||||||
                            Utils.closeQuietly(output);
 | 
					 | 
				
			||||||
                            Utils.closeQuietly(input);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        certs = je.getCertificates();
 | 
					 | 
				
			||||||
                    } catch (SecurityException e) {
 | 
					 | 
				
			||||||
                        Log.e("FDroid", "Invalid hash for index file");
 | 
					 | 
				
			||||||
                        return "Invalid hash for index file";
 | 
					 | 
				
			||||||
                    } finally {
 | 
					 | 
				
			||||||
                        if (jar != null) {
 | 
					 | 
				
			||||||
                            jar.close();
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (certs == null) {
 | 
					 | 
				
			||||||
                        Log.d("FDroid", "No signature found in index");
 | 
					 | 
				
			||||||
                        return "No signature found in index";
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    Log.d("FDroid", "Index has " + certs.length + " signature"
 | 
					 | 
				
			||||||
                            + (certs.length > 1 ? "s." : "."));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    boolean match = false;
 | 
					 | 
				
			||||||
                    for (Certificate cert : certs) {
 | 
					 | 
				
			||||||
                        String certdata = Hasher.hex(cert.getEncoded());
 | 
					 | 
				
			||||||
                        if (repo.pubkey.equals(certdata)) {
 | 
					 | 
				
			||||||
                            match = true;
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (!match) {
 | 
					 | 
				
			||||||
                        Log.d("FDroid", "Index signature mismatch");
 | 
					 | 
				
			||||||
                        return "Index signature mismatch";
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // It's an old-fashioned unsigned repo...
 | 
					 | 
				
			||||||
                Log.d("FDroid", "Getting unsigned index from " + repo.address);
 | 
					 | 
				
			||||||
                Bundle eventData = createProgressData(repo.address);
 | 
					 | 
				
			||||||
                ProgressListener.Event event = new ProgressListener.Event(
 | 
					 | 
				
			||||||
                        RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, eventData);
 | 
					 | 
				
			||||||
                code = getRemoteFile(ctx, repo.address + "/index.xml",
 | 
					 | 
				
			||||||
                        "tempindex.xml", repo.lastetag, newetag,
 | 
					 | 
				
			||||||
                        progressListener, event);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (code == 200) {
 | 
					 | 
				
			||||||
                // Process the index...
 | 
					 | 
				
			||||||
                SAXParserFactory spf = SAXParserFactory.newInstance();
 | 
					 | 
				
			||||||
                SAXParser sp = spf.newSAXParser();
 | 
					 | 
				
			||||||
                XMLReader xr = sp.getXMLReader();
 | 
					 | 
				
			||||||
                RepoXMLHandler handler = new RepoXMLHandler(repo, apps, progressListener);
 | 
					 | 
				
			||||||
                xr.setContentHandler(handler);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                File tempIndex = new File(ctx.getFilesDir() + "/tempindex.xml");
 | 
					 | 
				
			||||||
                BufferedReader r = new BufferedReader(new FileReader(tempIndex));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // A bit of a hack, this might return false positives if an apps description
 | 
					 | 
				
			||||||
                // or some other part of the XML file contains this, but it is a pretty good
 | 
					 | 
				
			||||||
                // estimate and makes the progress counter more informative.
 | 
					 | 
				
			||||||
                // As with asking the server about the size of the index before downloading,
 | 
					 | 
				
			||||||
                // this also has a time tradeoff. It takes about three seconds to iterate
 | 
					 | 
				
			||||||
                // through the file and count 600 apps on a slow emulator (v17), but if it is
 | 
					 | 
				
			||||||
                // taking two minutes to update, the three second wait may be worth it.
 | 
					 | 
				
			||||||
                final String APPLICATION = "<application";
 | 
					 | 
				
			||||||
                handler.setTotalAppCount(Utils.countSubstringOccurrence(tempIndex, APPLICATION));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                InputSource is = new InputSource(r);
 | 
					 | 
				
			||||||
                xr.parse(is);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (handler.pubkey != null && repo.pubkey == null) {
 | 
					 | 
				
			||||||
                    // We read an unsigned index, but that indicates that
 | 
					 | 
				
			||||||
                    // a signed version is now available...
 | 
					 | 
				
			||||||
                    Log.d("FDroid",
 | 
					 | 
				
			||||||
                            "Public key found - switching to signed repo for future updates");
 | 
					 | 
				
			||||||
                    repo.pubkey = handler.pubkey;
 | 
					 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        DB db = DB.getDB();
 | 
					 | 
				
			||||||
                        db.updateRepoByAddress(repo);
 | 
					 | 
				
			||||||
                    } finally {
 | 
					 | 
				
			||||||
                        DB.releaseDB();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (handler.maxage != null) {
 | 
					 | 
				
			||||||
                    int maxage = Integer.parseInt(handler.maxage);
 | 
					 | 
				
			||||||
                    if (maxage != repo.maxage) {
 | 
					 | 
				
			||||||
                        Log.d("FDroid",
 | 
					 | 
				
			||||||
                                "Repo specified a new maximum age - updated");
 | 
					 | 
				
			||||||
                        repo.maxage = maxage;
 | 
					 | 
				
			||||||
                        try {
 | 
					 | 
				
			||||||
                            DB db = DB.getDB();
 | 
					 | 
				
			||||||
                            db.updateRepoByAddress(repo);
 | 
					 | 
				
			||||||
                        } finally {
 | 
					 | 
				
			||||||
                            DB.releaseDB();
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            } else if (code == 304) {
 | 
					 | 
				
			||||||
                // The index is unchanged since we last read it. We just mark
 | 
					 | 
				
			||||||
                // everything that came from this repo as being updated.
 | 
					 | 
				
			||||||
                Log.d("FDroid", "Repo index for " + repo.address
 | 
					 | 
				
			||||||
                        + " is up to date (by etag)");
 | 
					 | 
				
			||||||
                keeprepos.add(repo.id);
 | 
					 | 
				
			||||||
                // Make sure we give back the same etag. (The 200 route will
 | 
					 | 
				
			||||||
                // have supplied a new one.
 | 
					 | 
				
			||||||
                newetag.append(repo.lastetag);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return "Failed to read index - HTTP response "
 | 
					 | 
				
			||||||
                        + Integer.toString(code);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } catch (SSLHandshakeException sslex) {
 | 
					 | 
				
			||||||
            Log.e("FDroid", "SSLHandShakeException updating from "
 | 
					 | 
				
			||||||
                    + repo.address + ":\n" + Log.getStackTraceString(sslex));
 | 
					 | 
				
			||||||
            return "A problem occurred while establishing an SSL connection. If this problem persists, AND you have a very old device, you could try using http instead of https for the repo URL.";
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					 | 
				
			||||||
            Log.e("FDroid", "Exception updating from " + repo.address + ":\n"
 | 
					 | 
				
			||||||
                    + Log.getStackTraceString(e));
 | 
					 | 
				
			||||||
            return "Failed to update - " + e.getMessage();
 | 
					 | 
				
			||||||
        } finally {
 | 
					 | 
				
			||||||
            ctx.deleteFile("tempindex.xml");
 | 
					 | 
				
			||||||
            ctx.deleteFile("tempindex.jar");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void setTotalAppCount(int totalAppCount) {
 | 
					    public void setTotalAppCount(int totalAppCount) {
 | 
				
			||||||
        this.totalAppCount = totalAppCount;
 | 
					        this.totalAppCount = totalAppCount;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,6 @@ import android.content.Context;
 | 
				
			|||||||
import android.content.Intent;
 | 
					import android.content.Intent;
 | 
				
			||||||
import android.content.SharedPreferences;
 | 
					import android.content.SharedPreferences;
 | 
				
			||||||
import android.content.SharedPreferences.Editor;
 | 
					import android.content.SharedPreferences.Editor;
 | 
				
			||||||
import android.graphics.BitmapFactory;
 | 
					 | 
				
			||||||
import android.net.ConnectivityManager;
 | 
					import android.net.ConnectivityManager;
 | 
				
			||||||
import android.net.NetworkInfo;
 | 
					import android.net.NetworkInfo;
 | 
				
			||||||
import android.os.Build;
 | 
					import android.os.Build;
 | 
				
			||||||
@ -38,6 +37,7 @@ import android.os.ResultReceiver;
 | 
				
			|||||||
import android.os.SystemClock;
 | 
					import android.os.SystemClock;
 | 
				
			||||||
import android.preference.PreferenceManager;
 | 
					import android.preference.PreferenceManager;
 | 
				
			||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.updater.RepoUpdater;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.support.v4.app.NotificationCompat;
 | 
					import android.support.v4.app.NotificationCompat;
 | 
				
			||||||
import android.support.v4.app.TaskStackBuilder;
 | 
					import android.support.v4.app.TaskStackBuilder;
 | 
				
			||||||
@ -164,45 +164,39 @@ public class UpdateService extends IntentService implements ProgressListener {
 | 
				
			|||||||
            // database while we do all the downloading, etc...
 | 
					            // database while we do all the downloading, etc...
 | 
				
			||||||
            int updates = 0;
 | 
					            int updates = 0;
 | 
				
			||||||
            List<DB.Repo> repos;
 | 
					            List<DB.Repo> repos;
 | 
				
			||||||
 | 
					            List<DB.App> apps;
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                DB db = DB.getDB();
 | 
					                DB db = DB.getDB();
 | 
				
			||||||
                repos = db.getRepos();
 | 
					                repos = db.getRepos();
 | 
				
			||||||
 | 
					                apps = db.getApps(false);
 | 
				
			||||||
            } finally {
 | 
					            } finally {
 | 
				
			||||||
                DB.releaseDB();
 | 
					                DB.releaseDB();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Process each repo...
 | 
					            // Process each repo...
 | 
				
			||||||
            List<DB.App> apps;
 | 
					 | 
				
			||||||
            List<DB.App> updatingApps = new ArrayList<DB.App>();
 | 
					            List<DB.App> updatingApps = new ArrayList<DB.App>();
 | 
				
			||||||
            List<Integer> keeprepos = new ArrayList<Integer>();
 | 
					            List<Integer> keeprepos = new ArrayList<Integer>();
 | 
				
			||||||
            boolean success = true;
 | 
					            boolean success = true;
 | 
				
			||||||
            boolean changes = false;
 | 
					            boolean changes = false;
 | 
				
			||||||
            for (DB.Repo repo : repos) {
 | 
					            for (DB.Repo repo : repos) {
 | 
				
			||||||
                if (repo.inuse) {
 | 
					                if (!repo.inuse) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
                    sendStatus(
 | 
					                }
 | 
				
			||||||
                            STATUS_INFO,
 | 
					                sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
 | 
				
			||||||
                            getString(R.string.status_connecting_to_repo,
 | 
					                RepoUpdater updater = RepoUpdater.createUpdaterFor(getBaseContext(), repo);
 | 
				
			||||||
                                    repo.address));
 | 
					                updater.setProgressListener(this);
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
                    StringBuilder newetag = new StringBuilder();
 | 
					                    updater.update();
 | 
				
			||||||
                    String err = RepoXMLHandler.doUpdate(getBaseContext(),
 | 
					                    if (updater.hasChanged()) {
 | 
				
			||||||
                            repo, updatingApps, newetag, keeprepos, this);
 | 
					                        updatingApps.addAll(updater.getApps());
 | 
				
			||||||
                    if (err == null) {
 | 
					                        changes = true;
 | 
				
			||||||
                        String nt = newetag.toString();
 | 
					 | 
				
			||||||
                        if (!nt.equals(repo.lastetag)) {
 | 
					 | 
				
			||||||
                            repo.lastetag = newetag.toString();
 | 
					 | 
				
			||||||
                            changes = true;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        success = false;
 | 
					                        keeprepos.add(repo.id);
 | 
				
			||||||
                        err = "Update failed for " + repo.address + " - " + err;
 | 
					 | 
				
			||||||
                        Log.d("FDroid", err);
 | 
					 | 
				
			||||||
                        if (errmsg.length() == 0)
 | 
					 | 
				
			||||||
                            errmsg = err;
 | 
					 | 
				
			||||||
                        else
 | 
					 | 
				
			||||||
                            errmsg += "\n" + err;
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                } catch (RepoUpdater.UpdateException e) {
 | 
				
			||||||
 | 
					                    errmsg += (errmsg.length() == 0 ? "" : "\n") + e.getMessage();
 | 
				
			||||||
 | 
					                    Log.e("FDroid", "Error updating repository " + repo.address + ": " + e.getMessage());
 | 
				
			||||||
 | 
					                    Log.e("FDroid", Log.getStackTraceString(e));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -348,23 +342,17 @@ public class UpdateService extends IntentService implements ProgressListener {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void onProgress(ProgressListener.Event event) {
 | 
					    public void onProgress(ProgressListener.Event event) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        String message = "";
 | 
					        String message = "";
 | 
				
			||||||
        if (event.type == RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD) {
 | 
					        if (event.type == RepoUpdater.PROGRESS_TYPE_DOWNLOAD) {
 | 
				
			||||||
            String repoAddress = event.data
 | 
					            String repoAddress    = event.data.getString(RepoUpdater.PROGRESS_DATA_REPO);
 | 
				
			||||||
                    .getString(RepoXMLHandler.PROGRESS_DATA_REPO);
 | 
					            String downloadedSize = Utils.getFriendlySize( event.progress );
 | 
				
			||||||
            String downloadedSize = Utils.getFriendlySize(event.progress);
 | 
					            String totalSize      = Utils.getFriendlySize( event.total );
 | 
				
			||||||
            String totalSize = Utils.getFriendlySize(event.total);
 | 
					            int percent           = (int)((double)event.progress/event.total * 100);
 | 
				
			||||||
            int percent = (int) ((double) event.progress / event.total * 100);
 | 
					            message = getString(R.string.status_download, repoAddress, downloadedSize, totalSize, percent);
 | 
				
			||||||
            message = getString(R.string.status_download, repoAddress,
 | 
					        } else if (event.type == RepoUpdater.PROGRESS_TYPE_PROCESS_XML) {
 | 
				
			||||||
                    downloadedSize, totalSize, percent);
 | 
					            String repoAddress    = event.data.getString(RepoUpdater.PROGRESS_DATA_REPO);
 | 
				
			||||||
        } else if (event.type == RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML) {
 | 
					            message = getString(R.string.status_processing_xml, repoAddress, event.progress, event.total);
 | 
				
			||||||
            String repoAddress = event.data
 | 
					 | 
				
			||||||
                    .getString(RepoXMLHandler.PROGRESS_DATA_REPO);
 | 
					 | 
				
			||||||
            message = getString(R.string.status_processing_xml, repoAddress,
 | 
					 | 
				
			||||||
                    event.progress, event.total);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        sendStatus(STATUS_INFO, message);
 | 
					        sendStatus(STATUS_INFO, message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,7 @@ import java.io.FileReader;
 | 
				
			|||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					import java.text.SimpleDateFormat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public final class Utils {
 | 
					public final class Utils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -33,6 +34,10 @@ public final class Utils {
 | 
				
			|||||||
    private static final String[] FRIENDLY_SIZE_FORMAT = {
 | 
					    private static final String[] FRIENDLY_SIZE_FORMAT = {
 | 
				
			||||||
            "%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB" };
 | 
					            "%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final SimpleDateFormat LOG_DATE_FORMAT =
 | 
				
			||||||
 | 
					            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void copy(InputStream input, OutputStream output)
 | 
					    public static void copy(InputStream input, OutputStream output)
 | 
				
			||||||
            throws IOException {
 | 
					            throws IOException {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										138
									
								
								src/org/fdroid/fdroid/net/Downloader.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/org/fdroid/fdroid/net/Downloader.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid.net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.*;
 | 
				
			||||||
 | 
					import java.net.*;
 | 
				
			||||||
 | 
					import android.content.*;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class Downloader {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String HEADER_IF_NONE_MATCH = "If-None-Match";
 | 
				
			||||||
 | 
					    private static final String HEADER_FIELD_ETAG = "ETag";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private URL sourceUrl;
 | 
				
			||||||
 | 
					    private OutputStream outputStream;
 | 
				
			||||||
 | 
					    private ProgressListener progressListener = null;
 | 
				
			||||||
 | 
					    private ProgressListener.Event progressEvent = null;
 | 
				
			||||||
 | 
					    private String eTag = null;
 | 
				
			||||||
 | 
					    private final File outputFile;
 | 
				
			||||||
 | 
					    private HttpURLConnection connection;
 | 
				
			||||||
 | 
					    private int statusCode = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The context is required for opening the file to write to.
 | 
				
			||||||
 | 
					    public Downloader(String source, String destFile, Context ctx)
 | 
				
			||||||
 | 
					            throws FileNotFoundException, MalformedURLException {
 | 
				
			||||||
 | 
					        sourceUrl    = new URL(source);
 | 
				
			||||||
 | 
					        outputStream = ctx.openFileOutput(destFile, Context.MODE_PRIVATE);
 | 
				
			||||||
 | 
					        outputFile   = new File(ctx.getFilesDir() + File.separator + destFile);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Downloads to a temporary file, which *you must delete yourself when
 | 
				
			||||||
 | 
					     * you are done*.
 | 
				
			||||||
 | 
					     * @see org.fdroid.fdroid.net.Downloader#getFile()
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public Downloader(String source, Context ctx) throws IOException {
 | 
				
			||||||
 | 
					        // http://developer.android.com/guide/topics/data/data-storage.html#InternalCache
 | 
				
			||||||
 | 
					        outputFile = File.createTempFile("dl-", "", ctx.getCacheDir());
 | 
				
			||||||
 | 
					        outputStream = new FileOutputStream(outputFile);
 | 
				
			||||||
 | 
					        sourceUrl = new URL(source);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Downloader(String source, OutputStream output)
 | 
				
			||||||
 | 
					            throws MalformedURLException {
 | 
				
			||||||
 | 
					        sourceUrl    = new URL(source);
 | 
				
			||||||
 | 
					        outputStream = output;
 | 
				
			||||||
 | 
					        outputFile   = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setProgressListener(ProgressListener progressListener,
 | 
				
			||||||
 | 
					                                    ProgressListener.Event progressEvent) {
 | 
				
			||||||
 | 
					        this.progressListener = progressListener;
 | 
				
			||||||
 | 
					        this.progressEvent = progressEvent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Only available if you passed a context object into the constructor
 | 
				
			||||||
 | 
					     * (rather than an outputStream, which may or  may not be associated with
 | 
				
			||||||
 | 
					     * a file).
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public File getFile() {
 | 
				
			||||||
 | 
					        return outputFile;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Only available after downloading a file.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public int getStatusCode() {
 | 
				
			||||||
 | 
					        return statusCode;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * If you ask for the eTag before calling download(), you will get the
 | 
				
			||||||
 | 
					     * same one you passed in (if any). If you call it after download(), you
 | 
				
			||||||
 | 
					     * will get the new eTag from the server, or null if there was none.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public String getETag() {
 | 
				
			||||||
 | 
					        return eTag;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * If this eTag matches that returned by the server, then no download will
 | 
				
			||||||
 | 
					     * take place, and a status code of 304 will be returned by download().
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public void setETag(String eTag) {
 | 
				
			||||||
 | 
					        this.eTag = eTag;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Get a remote file. Returns the HTTP response code.
 | 
				
			||||||
 | 
					    // If 'etag' is not null, it's passed to the server as an If-None-Match
 | 
				
			||||||
 | 
					    // header, in which case expect a 304 response if nothing changed.
 | 
				
			||||||
 | 
					    // In the event of a 200 response ONLY, 'retag' (which should be passed
 | 
				
			||||||
 | 
					    // empty) may contain an etag value for the response, or it may be left
 | 
				
			||||||
 | 
					    // empty if none was available.
 | 
				
			||||||
 | 
					    public int download() throws IOException {
 | 
				
			||||||
 | 
					        connection = (HttpURLConnection)sourceUrl.openConnection();
 | 
				
			||||||
 | 
					        setupCacheCheck();
 | 
				
			||||||
 | 
					        statusCode = connection.getResponseCode();
 | 
				
			||||||
 | 
					        if (statusCode == 200) {
 | 
				
			||||||
 | 
					            setupProgressListener();
 | 
				
			||||||
 | 
					            InputStream input = null;
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                input = connection.getInputStream();
 | 
				
			||||||
 | 
					                Utils.copy(input, outputStream,
 | 
				
			||||||
 | 
					                        progressListener, progressEvent);
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                Utils.closeQuietly(outputStream);
 | 
				
			||||||
 | 
					                Utils.closeQuietly(input);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            updateCacheCheck();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return statusCode;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected void setupCacheCheck() {
 | 
				
			||||||
 | 
					        if (eTag != null) {
 | 
				
			||||||
 | 
					            connection.setRequestProperty(HEADER_IF_NONE_MATCH, eTag);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected void updateCacheCheck() {
 | 
				
			||||||
 | 
					        eTag = connection.getHeaderField(HEADER_FIELD_ETAG);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected void setupProgressListener() {
 | 
				
			||||||
 | 
					        if (progressListener != null && progressEvent != null) {
 | 
				
			||||||
 | 
					            // Testing in the emulator for me, showed that figuring out the
 | 
				
			||||||
 | 
					            // filesize took about 1 to 1.5 seconds.
 | 
				
			||||||
 | 
					            // To put this in context, downloading a repo of:
 | 
				
			||||||
 | 
					            //  - 400k takes ~6 seconds
 | 
				
			||||||
 | 
					            //  - 5k   takes ~3 seconds
 | 
				
			||||||
 | 
					            // on my connection. I think the 1/1.5 seconds is worth it,
 | 
				
			||||||
 | 
					            // because as the repo grows, the tradeoff will
 | 
				
			||||||
 | 
					            // become more worth it.
 | 
				
			||||||
 | 
					            progressEvent.total = connection.getContentLength();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										243
									
								
								src/org/fdroid/fdroid/updater/RepoUpdater.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								src/org/fdroid/fdroid/updater/RepoUpdater.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,243 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid.updater;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import android.os.Bundle;
 | 
				
			||||||
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.DB;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.ProgressListener;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.RepoXMLHandler;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.net.Downloader;
 | 
				
			||||||
 | 
					import org.xml.sax.InputSource;
 | 
				
			||||||
 | 
					import org.xml.sax.SAXException;
 | 
				
			||||||
 | 
					import org.xml.sax.XMLReader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.net.ssl.SSLHandshakeException;
 | 
				
			||||||
 | 
					import javax.xml.parsers.ParserConfigurationException;
 | 
				
			||||||
 | 
					import javax.xml.parsers.SAXParser;
 | 
				
			||||||
 | 
					import javax.xml.parsers.SAXParserFactory;
 | 
				
			||||||
 | 
					import java.io.*;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract public class RepoUpdater {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final int PROGRESS_TYPE_DOWNLOAD     = 1;
 | 
				
			||||||
 | 
					    public static final int PROGRESS_TYPE_PROCESS_XML  = 2;
 | 
				
			||||||
 | 
					    public static final String PROGRESS_DATA_REPO      = "repo";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static RepoUpdater createUpdaterFor(Context ctx, DB.Repo repo) {
 | 
				
			||||||
 | 
					        if (repo.pubkey != null) {
 | 
				
			||||||
 | 
					            return new SignedRepoUpdater(ctx, repo);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return new UnsignedRepoUpdater(ctx, repo);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected final Context context;
 | 
				
			||||||
 | 
					    protected final DB.Repo repo;
 | 
				
			||||||
 | 
					    protected final List<DB.App> apps = new ArrayList<DB.App>();
 | 
				
			||||||
 | 
					    protected boolean hasChanged = false;
 | 
				
			||||||
 | 
					    protected ProgressListener progressListener;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public RepoUpdater(Context ctx, DB.Repo repo) {
 | 
				
			||||||
 | 
					        this.context = ctx;
 | 
				
			||||||
 | 
					        this.repo    = repo;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setProgressListener(ProgressListener progressListener) {
 | 
				
			||||||
 | 
					        this.progressListener = progressListener;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean hasChanged() {
 | 
				
			||||||
 | 
					        return hasChanged;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<DB.App> getApps() {
 | 
				
			||||||
 | 
					        return apps;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean isInteractive() {
 | 
				
			||||||
 | 
					        return progressListener != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Return the index file if it is different than last time,
 | 
				
			||||||
 | 
					     * otherwise returns null to indicate that the file has not changed.
 | 
				
			||||||
 | 
					     * All error states will come via an UpdateException.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected abstract File getIndexFile() throws UpdateException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected abstract String getIndexAddress();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected Downloader downloadIndex() throws UpdateException {
 | 
				
			||||||
 | 
					        Bundle progressData = createProgressData(repo.address);
 | 
				
			||||||
 | 
					        Downloader downloader = null;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            downloader = new Downloader(getIndexAddress(), context);
 | 
				
			||||||
 | 
					            downloader.setETag(repo.lastetag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isInteractive()) {
 | 
				
			||||||
 | 
					                ProgressListener.Event event =
 | 
				
			||||||
 | 
					                    new ProgressListener.Event(
 | 
				
			||||||
 | 
					                        RepoUpdater.PROGRESS_TYPE_DOWNLOAD, progressData);
 | 
				
			||||||
 | 
					                downloader.setProgressListener(progressListener, event);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int status = downloader.download();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            repo.lastetag = downloader.getETag();
 | 
				
			||||||
 | 
					            if (status == 304) {
 | 
				
			||||||
 | 
					                // The index is unchanged since we last read it. We just mark
 | 
				
			||||||
 | 
					                // everything that came from this repo as being updated.
 | 
				
			||||||
 | 
					                Log.d("FDroid", "Repo index for " + repo.address
 | 
				
			||||||
 | 
					                        + " is up to date (by etag)");
 | 
				
			||||||
 | 
					            } else if (status == 200) {
 | 
				
			||||||
 | 
					                hasChanged = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Is there any code other than 200 which still returns
 | 
				
			||||||
 | 
					                // content? Just in case, lets try to clean up.
 | 
				
			||||||
 | 
					                if (downloader.getFile() != null) {
 | 
				
			||||||
 | 
					                    downloader.getFile().delete();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                throw new UpdateException(
 | 
				
			||||||
 | 
					                        repo,
 | 
				
			||||||
 | 
					                        "Failed to update repo " + repo.address +
 | 
				
			||||||
 | 
					                        " - HTTP response " + status);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (SSLHandshakeException e) {
 | 
				
			||||||
 | 
					            throw new UpdateException(
 | 
				
			||||||
 | 
					                    repo,
 | 
				
			||||||
 | 
					                    "A problem occurred while establishing an SSL " +
 | 
				
			||||||
 | 
					                    "connection. If this problem persists, AND you have a " +
 | 
				
			||||||
 | 
					                    "very old device, you could try using http instead of " +
 | 
				
			||||||
 | 
					                    "https for the repo URL.",
 | 
				
			||||||
 | 
					                    e );
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            if (downloader != null && downloader.getFile() != null) {
 | 
				
			||||||
 | 
					                downloader.getFile().delete();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            throw new UpdateException(
 | 
				
			||||||
 | 
					                    repo,
 | 
				
			||||||
 | 
					                    "Error getting index file from " + repo.address,
 | 
				
			||||||
 | 
					                    e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return downloader;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Bundle createProgressData(String repoAddress) {
 | 
				
			||||||
 | 
					        Bundle data = new Bundle();
 | 
				
			||||||
 | 
					        data.putString(PROGRESS_DATA_REPO, repoAddress);
 | 
				
			||||||
 | 
					        return data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private int estimateAppCount(File indexFile) {
 | 
				
			||||||
 | 
					        int count = -1;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // A bit of a hack, this might return false positives if an apps description
 | 
				
			||||||
 | 
					            // or some other part of the XML file contains this, but it is a pretty good
 | 
				
			||||||
 | 
					            // estimate and makes the progress counter more informative.
 | 
				
			||||||
 | 
					            // As with asking the server about the size of the index before downloading,
 | 
				
			||||||
 | 
					            // this also has a time tradeoff. It takes about three seconds to iterate
 | 
				
			||||||
 | 
					            // through the file and count 600 apps on a slow emulator (v17), but if it is
 | 
				
			||||||
 | 
					            // taking two minutes to update, the three second wait may be worth it.
 | 
				
			||||||
 | 
					            final String APPLICATION = "<application";
 | 
				
			||||||
 | 
					            count = Utils.countSubstringOccurrence(indexFile, APPLICATION);
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            // Do nothing. Leave count at default -1 value.
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return count;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void update() throws UpdateException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        File indexFile = null;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            indexFile = getIndexFile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Process the index...
 | 
				
			||||||
 | 
					            SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
 | 
				
			||||||
 | 
					            XMLReader reader = parser.getXMLReader();
 | 
				
			||||||
 | 
					            RepoXMLHandler handler = new RepoXMLHandler(repo, apps, progressListener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isInteractive()) {
 | 
				
			||||||
 | 
					                // Only bother spending the time to count the expected apps
 | 
				
			||||||
 | 
					                // if we can show that to the user...
 | 
				
			||||||
 | 
					                handler.setTotalAppCount(estimateAppCount(indexFile));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            reader.setContentHandler(handler);
 | 
				
			||||||
 | 
					            InputSource is = new InputSource(
 | 
				
			||||||
 | 
					                    new BufferedReader(new FileReader(indexFile)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            reader.parse(is);
 | 
				
			||||||
 | 
					            updateRepo(handler.getPubKey(), handler.getMaxAge());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } catch (SAXException e) {
 | 
				
			||||||
 | 
					            throw new UpdateException(
 | 
				
			||||||
 | 
					                    repo, "Error parsing index for repo " + repo.address, e);
 | 
				
			||||||
 | 
					        } catch (FileNotFoundException e) {
 | 
				
			||||||
 | 
					            throw new UpdateException(
 | 
				
			||||||
 | 
					                    repo, "Error parsing index for repo " + repo.address, e);
 | 
				
			||||||
 | 
					        } catch (ParserConfigurationException e) {
 | 
				
			||||||
 | 
					            throw new UpdateException(
 | 
				
			||||||
 | 
					                    repo, "Error parsing index for repo " + repo.address, e);
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            throw new UpdateException(
 | 
				
			||||||
 | 
					                    repo, "Error parsing index for repo " + repo.address, e);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            if (indexFile != null && indexFile.exists()) {
 | 
				
			||||||
 | 
					                indexFile.delete();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void updateRepo(String publicKey, int maxAge) {
 | 
				
			||||||
 | 
					        boolean changed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // We read an unsigned index, but that indicates that
 | 
				
			||||||
 | 
					        // a signed version is now available...
 | 
				
			||||||
 | 
					        if (publicKey != null && repo.pubkey == null) {
 | 
				
			||||||
 | 
					            changed = true;
 | 
				
			||||||
 | 
					            // TODO: Spend the time *now* going to get the etag of the signed
 | 
				
			||||||
 | 
					            // repo, so that we can prevent downloading it next time. Otherwise
 | 
				
			||||||
 | 
					            // next time we update, we have to download the signed index
 | 
				
			||||||
 | 
					            // in its entirety, regardless of if it contains the same
 | 
				
			||||||
 | 
					            // information as the unsigned one does not...
 | 
				
			||||||
 | 
					            Log.d("FDroid", "Public key found - switching to signed repo " +
 | 
				
			||||||
 | 
					                    "for future updates");
 | 
				
			||||||
 | 
					            repo.pubkey = publicKey;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (repo.maxage != maxAge) {
 | 
				
			||||||
 | 
					            changed = true;
 | 
				
			||||||
 | 
					            repo.maxage = maxAge;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (changed) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                DB db = DB.getDB();
 | 
				
			||||||
 | 
					                db.updateRepoByAddress(repo);
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                DB.releaseDB();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static class UpdateException extends Exception {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public final DB.Repo repo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public UpdateException(DB.Repo repo, String message) {
 | 
				
			||||||
 | 
					            super(message);
 | 
				
			||||||
 | 
					            this.repo = repo;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public UpdateException(DB.Repo repo, String message, Exception cause) {
 | 
				
			||||||
 | 
					            super(message, cause);
 | 
				
			||||||
 | 
					            this.repo = repo;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										114
									
								
								src/org/fdroid/fdroid/updater/SignedRepoUpdater.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/org/fdroid/fdroid/updater/SignedRepoUpdater.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid.updater;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.DB;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.Hasher;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.R;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.net.Downloader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.*;
 | 
				
			||||||
 | 
					import java.security.cert.Certificate;
 | 
				
			||||||
 | 
					import java.util.Date;
 | 
				
			||||||
 | 
					import java.util.jar.JarEntry;
 | 
				
			||||||
 | 
					import java.util.jar.JarFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class SignedRepoUpdater extends RepoUpdater {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SignedRepoUpdater(Context ctx, DB.Repo repo) {
 | 
				
			||||||
 | 
					        super(ctx, repo);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private boolean verifyCerts(JarEntry item) throws UpdateException {
 | 
				
			||||||
 | 
					        Certificate[] certs = item.getCertificates();
 | 
				
			||||||
 | 
					        if (certs == null || certs.length == 0) {
 | 
				
			||||||
 | 
					            throw new UpdateException(repo, "No signature found in index");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Log.d("FDroid", "Index has " + certs.length + " signature(s)");
 | 
				
			||||||
 | 
					        boolean match = false;
 | 
				
			||||||
 | 
					        for (Certificate cert : certs) {
 | 
				
			||||||
 | 
					            String certdata = Hasher.hex(cert);
 | 
				
			||||||
 | 
					            if (repo.pubkey.equals(certdata)) {
 | 
				
			||||||
 | 
					                match = true;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return match;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected File extractIndexFromJar(File indexJar) throws UpdateException {
 | 
				
			||||||
 | 
					        File indexFile  = null;
 | 
				
			||||||
 | 
					        JarFile jarFile = null;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            jarFile = new JarFile(indexJar, true);
 | 
				
			||||||
 | 
					            JarEntry indexEntry = (JarEntry)jarFile.getEntry("index.xml");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            indexFile  = File.createTempFile("index-", ".xml", context.getFilesDir());
 | 
				
			||||||
 | 
					            InputStream input = null;
 | 
				
			||||||
 | 
					            OutputStream output = null;
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                input = jarFile.getInputStream(indexEntry);
 | 
				
			||||||
 | 
					                output = new FileOutputStream(indexFile);
 | 
				
			||||||
 | 
					                Utils.copy(input, output);
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                Utils.closeQuietly(output);
 | 
				
			||||||
 | 
					                Utils.closeQuietly(input);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Can only read certificates from jar after it has been read
 | 
				
			||||||
 | 
					            // completely, so we put it after the copy above...
 | 
				
			||||||
 | 
					            if (!verifyCerts(indexEntry)) {
 | 
				
			||||||
 | 
					                indexFile.delete();
 | 
				
			||||||
 | 
					                throw new UpdateException(repo, "Index signature mismatch");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            if (indexFile != null) {
 | 
				
			||||||
 | 
					                indexFile.delete();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            throw new UpdateException(
 | 
				
			||||||
 | 
					                    repo, "Error opening signed index", e);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            if (jarFile != null) {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    jarFile.close();
 | 
				
			||||||
 | 
					                } catch (IOException ioe) {
 | 
				
			||||||
 | 
					                    // ignore
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return indexFile;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected String getIndexAddress() {
 | 
				
			||||||
 | 
					        return repo.address + "/index.jar?" + context.getString(R.string.version_name);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * As this is a signed repo - we download the jar file,
 | 
				
			||||||
 | 
					     * check the signature, and extract the index file
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected File getIndexFile() throws UpdateException {
 | 
				
			||||||
 | 
					        Date updateTime = new Date(System.currentTimeMillis());
 | 
				
			||||||
 | 
					        Log.d("FDroid", "Getting signed index from " + repo.address + " at " +
 | 
				
			||||||
 | 
					                Utils.LOG_DATE_FORMAT.format(updateTime));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Downloader downloader = downloadIndex();
 | 
				
			||||||
 | 
					        File indexJar  = downloader.getFile();
 | 
				
			||||||
 | 
					        File indexXml  = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Don't worry about checking the status code for 200. If it was a
 | 
				
			||||||
 | 
					        // successful download, then we will have a file ready to use:
 | 
				
			||||||
 | 
					        if (indexJar != null && indexJar.exists()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                indexXml = extractIndexFromJar(indexJar);
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                indexJar.delete();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return indexXml;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/org/fdroid/fdroid/updater/UnsignedRepoUpdater.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/org/fdroid/fdroid/updater/UnsignedRepoUpdater.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid.updater;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.DB;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.net.Downloader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class UnsignedRepoUpdater extends RepoUpdater {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public UnsignedRepoUpdater(Context ctx, DB.Repo repo) {
 | 
				
			||||||
 | 
					        super(ctx, repo);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected File getIndexFile() throws UpdateException {
 | 
				
			||||||
 | 
					        Log.d("FDroid", "Getting unsigned index from " + getIndexAddress());
 | 
				
			||||||
 | 
					        Downloader downloader = downloadIndex();
 | 
				
			||||||
 | 
					        return downloader.getFile();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected String getIndexAddress() {
 | 
				
			||||||
 | 
					        return repo.address + "/index.xml";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user