ensure the canonical repo URL is always included in mirrors list

The mirror logic assumes that it has a mirrors list with at least once
valid entry in it.  In the index format as defined by `fdroid update`,
there is always at least one valid URL: the canonical URL.  That also
means if there is only one item in the mirrors list, there are no
other URLs to try.

The initial state of the repos in the database also include the canonical
URL in the mirrors list so the mirror logic works on the first index
update.  That makes it possible to do the first index update via SD Card
or USB OTG drive.
This commit is contained in:
Hans-Christoph Steiner 2018-12-17 23:00:38 +01:00
parent 9380a6fd0f
commit ac1a5e0ad8
3 changed files with 50 additions and 16 deletions

View File

@ -62,8 +62,10 @@ import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
@ -284,7 +286,14 @@ public class IndexV1Updater extends IndexUpdater {
repo.name = getStringRepoValue(repoMap, "name"); repo.name = getStringRepoValue(repoMap, "name");
repo.icon = getStringRepoValue(repoMap, "icon"); repo.icon = getStringRepoValue(repoMap, "icon");
repo.description = getStringRepoValue(repoMap, "description"); repo.description = getStringRepoValue(repoMap, "description");
repo.mirrors = getStringArrayRepoValue(repoMap, "mirrors");
// ensure the canonical URL is included in the "mirrors" list
List<String> mirrorsList = getStringListRepoValue(repoMap, "mirrors");
HashSet<String> mirrors = new HashSet<>(mirrorsList.size() + 1);
mirrors.addAll(mirrorsList);
mirrors.add(repo.address);
repo.mirrors = mirrors.toArray(new String[mirrors.size()]);
// below are optional, can be default value // below are optional, can be default value
repo.maxage = getIntRepoValue(repoMap, "maxage"); repo.maxage = getIntRepoValue(repoMap, "maxage");
repo.version = getIntRepoValue(repoMap, "version"); repo.version = getIntRepoValue(repoMap, "version");
@ -372,13 +381,12 @@ public class IndexV1Updater extends IndexUpdater {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private String[] getStringArrayRepoValue(Map<String, Object> repoMap, String key) { private List<String> getStringListRepoValue(Map<String, Object> repoMap, String key) {
Object value = repoMap.get(key); Object value = repoMap.get(key);
if (value != null && value instanceof ArrayList) { if (value != null && value instanceof ArrayList) {
ArrayList<String> list = (ArrayList<String>) value; return (List<String>) value;
return list.toArray(new String[list.size()]);
} }
return null; return Collections.emptyList();
} }
private HashMap<String, Object> parseRepo(ObjectMapper mapper, JsonParser parser) throws IOException { private HashMap<String, Object> parseRepo(ObjectMapper mapper, JsonParser parser) throws IOException {

View File

@ -1394,6 +1394,12 @@ public class DBHelper extends SQLiteOpenHelper {
return exists; return exists;
} }
/**
* Insert a new repo into the database. This also initializes the list of
* "mirror" URLs. There should always be at least one URL there, since the
* logic in {@link org.fdroid.fdroid.FDroidApp#getMirror(String, Repo)}
* expects at least one entry in the mirrors list.
*/
private void insertRepo(SQLiteDatabase db, String name, String address, private void insertRepo(SQLiteDatabase db, String name, String address,
String description, String version, String enabled, String description, String version, String enabled,
String priority, String pushRequests, String pubKey) { String priority, String pushRequests, String pubKey) {
@ -1410,6 +1416,9 @@ public class DBHelper extends SQLiteOpenHelper {
values.put(RepoTable.Cols.LAST_ETAG, (String) null); values.put(RepoTable.Cols.LAST_ETAG, (String) null);
values.put(RepoTable.Cols.TIMESTAMP, 0); values.put(RepoTable.Cols.TIMESTAMP, 0);
String[] initializeMirrors = {address};
values.put(Schema.RepoTable.Cols.MIRRORS, Utils.serializeCommaSeparatedString(initializeMirrors));
switch (pushRequests) { switch (pushRequests) {
case "ignore": case "ignore":
values.put(RepoTable.Cols.PUSH_REQUESTS, Repo.PUSH_REQUEST_IGNORE); values.put(RepoTable.Cols.PUSH_REQUESTS, Repo.PUSH_REQUEST_IGNORE);

View File

@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
@ -338,20 +339,29 @@ public class Repo extends ValueObject {
} }
} }
/**
* The main repo URL is included in the mirror list, so it only makes
* sense to activate this logic if there are more than one entry in the
* mirror list.
*/
public boolean hasMirrors() { public boolean hasMirrors() {
return (mirrors != null && mirrors.length > 1) return (mirrors != null && mirrors.length > 1)
|| (userMirrors != null && userMirrors.length > 0); || (userMirrors != null && userMirrors.length > 0);
} }
/**
* @return {@link List} of valid URLs to reach this repo, including the canonical URL
*/
public List<String> getMirrorList() { public List<String> getMirrorList() {
final ArrayList<String> allMirrors = new ArrayList<String>(); final HashSet<String> allMirrors = new HashSet<>();
if (userMirrors != null) { if (userMirrors != null) {
allMirrors.addAll(Arrays.asList(userMirrors)); allMirrors.addAll(Arrays.asList(userMirrors));
} }
if (mirrors != null) { if (mirrors != null) {
allMirrors.addAll(Arrays.asList(mirrors)); allMirrors.addAll(Arrays.asList(mirrors));
} }
return allMirrors; allMirrors.add(address);
return new ArrayList<>(allMirrors);
} }
/** /**
@ -360,19 +370,26 @@ public class Repo extends ValueObject {
public int getMirrorCount() { public int getMirrorCount() {
int count = 0; int count = 0;
for (String m : getMirrorList()) { for (String m : getMirrorList()) {
if (!m.equals(address)) {
if (FDroidApp.isUsingTor()) { if (FDroidApp.isUsingTor()) {
count++; count++;
} else { } else if (!m.contains(".onion")) {
if (!m.contains(".onion")) {
count++; count++;
} }
} }
}
}
return count; return count;
} }
/**
* The mirror logic assumes that it has a mirrors list with at least once
* valid entry in it. In the index format as defined by {@code fdroid update},
* there is always at least one valid URL: the canonical URL. That also means
* if there is only one item in the mirrors list, there are no other URLs to try.
* <p>
* The initial state of the repos in the database also include the canonical
* URL in the mirrors list so the mirror logic works on the first index
* update. That makes it possible to do the first index update via SD Card
* or USB OTG drive.
*/
public String getMirror(String lastWorkingMirror) { public String getMirror(String lastWorkingMirror) {
if (TextUtils.isEmpty(lastWorkingMirror)) { if (TextUtils.isEmpty(lastWorkingMirror)) {
lastWorkingMirror = address; lastWorkingMirror = address;
@ -382,7 +399,7 @@ public class Repo extends ValueObject {
if (shuffledMirrors.size() > 1) { if (shuffledMirrors.size() > 1) {
for (String m : shuffledMirrors) { for (String m : shuffledMirrors) {
// Return a non default, and not last used mirror // Return a non default, and not last used mirror
if (!m.equals(address) && !m.equals(lastWorkingMirror)) { if (!m.equals(lastWorkingMirror)) {
if (FDroidApp.isUsingTor()) { if (FDroidApp.isUsingTor()) {
return m; return m;
} else { } else {