support adding custom mirrors to any existing repo, via "App Repo"

This lets people add any URL as a mirror to an existing repo.  The UX is
people add URLs via any of the normal ways of adding a new repo via Intents,
like clicking URLs, QRCodes, etc.
This commit is contained in:
Hans-Christoph Steiner 2018-03-29 14:53:38 +02:00
parent 89e04cc078
commit 2f0cb30ad0
6 changed files with 188 additions and 65 deletions

View File

@ -88,35 +88,36 @@ public class DBHelper extends SQLiteOpenHelper {
+ RepoTable.Cols.TIMESTAMP + " integer not null default 0, " + RepoTable.Cols.TIMESTAMP + " integer not null default 0, "
+ RepoTable.Cols.ICON + " string, " + RepoTable.Cols.ICON + " string, "
+ RepoTable.Cols.MIRRORS + " string, " + RepoTable.Cols.MIRRORS + " string, "
+ RepoTable.Cols.USER_MIRRORS + " string, "
+ RepoTable.Cols.PUSH_REQUESTS + " integer not null default " + Repo.PUSH_REQUEST_IGNORE + RepoTable.Cols.PUSH_REQUESTS + " integer not null default " + Repo.PUSH_REQUEST_IGNORE
+ ");"; + ");";
static final String CREATE_TABLE_APK = static final String CREATE_TABLE_APK =
"CREATE TABLE " + ApkTable.NAME + " ( " "CREATE TABLE " + ApkTable.NAME + " ( "
+ ApkTable.Cols.APP_ID + " integer not null, " + ApkTable.Cols.APP_ID + " integer not null, "
+ ApkTable.Cols.VERSION_NAME + " text not null, " + ApkTable.Cols.VERSION_NAME + " text not null, "
+ ApkTable.Cols.REPO_ID + " integer not null, " + ApkTable.Cols.REPO_ID + " integer not null, "
+ ApkTable.Cols.HASH + " text not null, " + ApkTable.Cols.HASH + " text not null, "
+ ApkTable.Cols.VERSION_CODE + " int not null," + ApkTable.Cols.VERSION_CODE + " int not null,"
+ ApkTable.Cols.NAME + " text not null, " + ApkTable.Cols.NAME + " text not null, "
+ ApkTable.Cols.SIZE + " int not null, " + ApkTable.Cols.SIZE + " int not null, "
+ ApkTable.Cols.SIGNATURE + " string, " + ApkTable.Cols.SIGNATURE + " string, "
+ ApkTable.Cols.SOURCE_NAME + " string, " + ApkTable.Cols.SOURCE_NAME + " string, "
+ ApkTable.Cols.MIN_SDK_VERSION + " integer, " + ApkTable.Cols.MIN_SDK_VERSION + " integer, "
+ ApkTable.Cols.TARGET_SDK_VERSION + " integer, " + ApkTable.Cols.TARGET_SDK_VERSION + " integer, "
+ ApkTable.Cols.MAX_SDK_VERSION + " integer, " + ApkTable.Cols.MAX_SDK_VERSION + " integer, "
+ ApkTable.Cols.OBB_MAIN_FILE + " string, " + ApkTable.Cols.OBB_MAIN_FILE + " string, "
+ ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string, " + ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string, "
+ ApkTable.Cols.OBB_PATCH_FILE + " string, " + ApkTable.Cols.OBB_PATCH_FILE + " string, "
+ ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string, " + ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string, "
+ ApkTable.Cols.REQUESTED_PERMISSIONS + " string, " + ApkTable.Cols.REQUESTED_PERMISSIONS + " string, "
+ ApkTable.Cols.FEATURES + " string, " + ApkTable.Cols.FEATURES + " string, "
+ ApkTable.Cols.NATIVE_CODE + " string, " + ApkTable.Cols.NATIVE_CODE + " string, "
+ ApkTable.Cols.HASH_TYPE + " string, " + ApkTable.Cols.HASH_TYPE + " string, "
+ ApkTable.Cols.ADDED_DATE + " string, " + ApkTable.Cols.ADDED_DATE + " string, "
+ ApkTable.Cols.IS_COMPATIBLE + " int not null, " + ApkTable.Cols.IS_COMPATIBLE + " int not null, "
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text" + ApkTable.Cols.INCOMPATIBLE_REASONS + " text"
+ ");"; + ");";
static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
+ " ( " + " ( "
@ -181,7 +182,7 @@ public class DBHelper extends SQLiteOpenHelper {
* app metadata id, because it can instead look through the primary key index. This can be * app metadata id, because it can instead look through the primary key index. This can be
* observed by flipping the order of the primary key columns, and noting the resulting sqlite * observed by flipping the order of the primary key columns, and noting the resulting sqlite
* logs along the lines of: * logs along the lines of:
* E/SQLiteLog(14164): (284) automatic index on fdroid_categoryAppMetadataJoin(appMetadataId) * E/SQLiteLog(14164): (284) automatic index on fdroid_categoryAppMetadataJoin(appMetadataId)
*/ */
static final String CREATE_TABLE_CAT_JOIN = "CREATE TABLE " + CatJoinTable.NAME static final String CREATE_TABLE_CAT_JOIN = "CREATE TABLE " + CatJoinTable.NAME
+ " ( " + " ( "
@ -214,7 +215,7 @@ public class DBHelper extends SQLiteOpenHelper {
+ "primary key(" + ApkAntiFeatureJoinTable.Cols.APK_ID + ", " + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + ") " + "primary key(" + ApkAntiFeatureJoinTable.Cols.APK_ID + ", " + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + ") "
+ " );"; + " );";
protected static final int DB_VERSION = 77; protected static final int DB_VERSION = 78;
private final Context context; private final Context context;
@ -321,6 +322,17 @@ public class DBHelper extends SQLiteOpenHelper {
addApkAntiFeatures(db, oldVersion); addApkAntiFeatures(db, oldVersion);
addIgnoreVulnPref(db, oldVersion); addIgnoreVulnPref(db, oldVersion);
addLiberapayID(db, oldVersion); addLiberapayID(db, oldVersion);
addUserMirrorsFields(db, oldVersion);
}
private void addUserMirrorsFields(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 78) {
return;
}
if (!columnExists(db, RepoTable.NAME, RepoTable.Cols.USER_MIRRORS)) {
Utils.debugLog(TAG, "Adding " + RepoTable.Cols.USER_MIRRORS + " field to " + RepoTable.NAME + " table in db.");
db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.USER_MIRRORS + " string;");
}
} }
private void addLiberapayID(SQLiteDatabase db, int oldVersion) { private void addLiberapayID(SQLiteDatabase db, int oldVersion) {
@ -581,7 +593,7 @@ public class DBHelper extends SQLiteOpenHelper {
updateRepoPriority(db, gpPubKey, gpArchiveAddress, 4); updateRepoPriority(db, gpPubKey, gpArchiveAddress, 4);
int priority = 5; int priority = 5;
String[] projection = new String[] {RepoTable.Cols.SIGNING_CERT, RepoTable.Cols.ADDRESS}; String[] projection = new String[]{RepoTable.Cols.SIGNING_CERT, RepoTable.Cols.ADDRESS};
// Order by ID, because that is a good analogy for the order in which they were added. // Order by ID, because that is a good analogy for the order in which they were added.
// The order in which they were added is likely the order they present in the ManageRepos activity. // The order in which they were added is likely the order they present in the ManageRepos activity.
@ -606,7 +618,7 @@ public class DBHelper extends SQLiteOpenHelper {
RepoTable.NAME, RepoTable.NAME,
values, values,
RepoTable.Cols.SIGNING_CERT + " = ? AND " + RepoTable.Cols.ADDRESS + " = ?", RepoTable.Cols.SIGNING_CERT + " = ? AND " + RepoTable.Cols.ADDRESS + " = ?",
new String[] {signingCert, address} new String[]{signingCert, address}
); );
} }
@ -630,15 +642,15 @@ public class DBHelper extends SQLiteOpenHelper {
Utils.debugLog(TAG, "Migrating app preferences to separate table"); Utils.debugLog(TAG, "Migrating app preferences to separate table");
db.execSQL( db.execSQL(
"INSERT INTO " + AppPrefsTable.NAME + " (" "INSERT INTO " + AppPrefsTable.NAME + " ("
+ AppPrefsTable.Cols.PACKAGE_NAME + ", " + AppPrefsTable.Cols.PACKAGE_NAME + ", "
+ AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ", " + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ", "
+ AppPrefsTable.Cols.IGNORE_ALL_UPDATES + AppPrefsTable.Cols.IGNORE_ALL_UPDATES
+ ") SELECT " + ") SELECT "
+ "id, " + "id, "
+ "ignoreThisUpdate, " + "ignoreThisUpdate, "
+ "ignoreAllUpdates " + "ignoreAllUpdates "
+ "FROM " + AppMetadataTable.NAME + " " + "FROM " + AppMetadataTable.NAME + " "
+ "WHERE ignoreThisUpdate > 0 OR ignoreAllUpdates > 0" + "WHERE ignoreThisUpdate > 0 OR ignoreAllUpdates > 0"
); );
resetTransient(db); resetTransient(db);
@ -687,7 +699,7 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL(createTableDdl); db.execSQL(createTableDdl);
String nonPackageNameFields = TextUtils.join(", ", new String[] { String nonPackageNameFields = TextUtils.join(", ", new String[]{
ApkTable.Cols.APP_ID, ApkTable.Cols.APP_ID,
ApkTable.Cols.VERSION_NAME, ApkTable.Cols.VERSION_NAME,
ApkTable.Cols.REPO_ID, ApkTable.Cols.REPO_ID,
@ -766,7 +778,7 @@ public class DBHelper extends SQLiteOpenHelper {
} }
List<Repo> oldrepos = new ArrayList<>(); List<Repo> oldrepos = new ArrayList<>();
Cursor cursor = db.query(RepoTable.NAME, Cursor cursor = db.query(RepoTable.NAME,
new String[] {RepoTable.Cols.ADDRESS, RepoTable.Cols.IN_USE, RepoTable.Cols.SIGNING_CERT}, new String[]{RepoTable.Cols.ADDRESS, RepoTable.Cols.IN_USE, RepoTable.Cols.SIGNING_CERT},
null, null, null, null, null); null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
@ -847,7 +859,7 @@ public class DBHelper extends SQLiteOpenHelper {
} }
List<Repo> oldrepos = new ArrayList<>(); List<Repo> oldrepos = new ArrayList<>();
Cursor cursor = db.query(RepoTable.NAME, Cursor cursor = db.query(RepoTable.NAME,
new String[] {RepoTable.Cols.ADDRESS, RepoTable.Cols.SIGNING_CERT}, new String[]{RepoTable.Cols.ADDRESS, RepoTable.Cols.SIGNING_CERT},
null, null, null, null, null); null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
@ -865,7 +877,7 @@ public class DBHelper extends SQLiteOpenHelper {
for (final Repo repo : oldrepos) { for (final Repo repo : oldrepos) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(RepoTable.Cols.FINGERPRINT, Utils.calcFingerprint(repo.signingCertificate)); values.put(RepoTable.Cols.FINGERPRINT, Utils.calcFingerprint(repo.signingCertificate));
db.update(RepoTable.NAME, values, RepoTable.Cols.ADDRESS + " = ?", new String[] {repo.address}); db.update(RepoTable.NAME, values, RepoTable.Cols.ADDRESS + " = ?", new String[]{repo.address});
} }
} }
@ -954,7 +966,7 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL(createTableDdl); db.execSQL(createTableDdl);
String nonIdFields = TextUtils.join(", ", new String[] { String nonIdFields = TextUtils.join(", ", new String[]{
RepoTable.Cols.ADDRESS, RepoTable.Cols.ADDRESS,
RepoTable.Cols.NAME, RepoTable.Cols.NAME,
RepoTable.Cols.DESCRIPTION, RepoTable.Cols.DESCRIPTION,
@ -1235,8 +1247,8 @@ public class DBHelper extends SQLiteOpenHelper {
} }
private static boolean tableExists(SQLiteDatabase db, String table) { private static boolean tableExists(SQLiteDatabase db, String table) {
Cursor cursor = db.query("sqlite_master", new String[] {"name"}, Cursor cursor = db.query("sqlite_master", new String[]{"name"},
"type = 'table' AND name = ?", new String[] {table}, null, null, null); "type = 'table' AND name = ?", new String[]{table}, null, null, null);
boolean exists = cursor.getCount() > 0; boolean exists = cursor.getCount() > 0;
cursor.close(); cursor.close();

View File

@ -26,13 +26,13 @@ package org.fdroid.fdroid.data;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.text.TextUtils; import android.text.TextUtils;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.RepoTable.Cols; import org.fdroid.fdroid.data.Schema.RepoTable.Cols;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
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;
@ -94,6 +94,11 @@ public class Repo extends ValueObject {
/** Official mirrors of this repo, considered automatically interchangeable */ /** Official mirrors of this repo, considered automatically interchangeable */
public String[] mirrors; public String[] mirrors;
/**
* Mirrors added by the user, either by UI input or by attaching removeable storage
*/
public String[] userMirrors;
/** How to treat push requests included in this repo's index XML */ /** How to treat push requests included in this repo's index XML */
public int pushRequests = PUSH_REQUEST_IGNORE; public int pushRequests = PUSH_REQUEST_IGNORE;
@ -160,6 +165,9 @@ public class Repo extends ValueObject {
case Cols.MIRRORS: case Cols.MIRRORS:
mirrors = Utils.parseCommaSeparatedString(cursor.getString(i)); mirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case Cols.USER_MIRRORS:
userMirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
break;
case Cols.PUSH_REQUESTS: case Cols.PUSH_REQUESTS:
pushRequests = cursor.getInt(i); pushRequests = cursor.getInt(i);
break; break;
@ -297,26 +305,43 @@ public class Repo extends ValueObject {
mirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.MIRRORS)); mirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.MIRRORS));
} }
if (values.containsKey(Cols.USER_MIRRORS)) {
userMirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.USER_MIRRORS));
}
if (values.containsKey(Cols.PUSH_REQUESTS)) { if (values.containsKey(Cols.PUSH_REQUESTS)) {
pushRequests = toInt(values.getAsInteger(Cols.PUSH_REQUESTS)); pushRequests = toInt(values.getAsInteger(Cols.PUSH_REQUESTS));
} }
} }
public boolean hasMirrors() { public boolean hasMirrors() {
return mirrors != null && mirrors.length > 1; return (mirrors != null && mirrors.length > 1)
|| (userMirrors != null && userMirrors.length > 0);
} }
public List<String> getMirrorList() {
final ArrayList<String> allMirrors = new ArrayList<String>();
if (userMirrors != null) {
allMirrors.addAll(Arrays.asList(userMirrors));
}
if (mirrors != null) {
allMirrors.addAll(Arrays.asList(mirrors));
}
return allMirrors;
}
/**
* Get the number of available mirrors, including the canonical repo.
*/
public int getMirrorCount() { public int getMirrorCount() {
int count = 0; int count = 0;
if (mirrors != null && mirrors.length > 1) { for (String m : getMirrorList()) {
for (String m: mirrors) { if (!m.equals(address)) {
if (!m.equals(address)) { if (FDroidApp.isUsingTor()) {
if (FDroidApp.isUsingTor()) { count++;
} else {
if (!m.contains(".onion")) {
count++; count++;
} else {
if (!m.contains(".onion")) {
count++;
}
} }
} }
} }
@ -328,7 +353,7 @@ public class Repo extends ValueObject {
if (TextUtils.isEmpty(lastWorkingMirror)) { if (TextUtils.isEmpty(lastWorkingMirror)) {
lastWorkingMirror = address; lastWorkingMirror = address;
} }
List<String> shuffledMirrors = Arrays.asList(mirrors); List<String> shuffledMirrors = getMirrorList();
Collections.shuffle(shuffledMirrors); Collections.shuffle(shuffledMirrors);
if (shuffledMirrors.size() > 1) { if (shuffledMirrors.size() > 1) {
for (String m : shuffledMirrors) { for (String m : shuffledMirrors) {

View File

@ -362,12 +362,13 @@ public interface Schema {
String TIMESTAMP = "timestamp"; String TIMESTAMP = "timestamp";
String ICON = "icon"; String ICON = "icon";
String MIRRORS = "mirrors"; String MIRRORS = "mirrors";
String USER_MIRRORS = "userMirrors";
String PUSH_REQUESTS = "pushRequests"; String PUSH_REQUESTS = "pushRequests";
String[] ALL = { String[] ALL = {
_ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, SIGNING_CERT, _ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, SIGNING_CERT,
FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP, FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP,
USERNAME, PASSWORD, TIMESTAMP, ICON, MIRRORS, PUSH_REQUESTS, USERNAME, PASSWORD, TIMESTAMP, ICON, MIRRORS, USER_MIRRORS, PUSH_REQUESTS,
}; };
} }
} }

View File

@ -61,12 +61,15 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema.RepoTable; import org.fdroid.fdroid.data.Schema.RepoTable;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@SuppressWarnings("LineLength") @SuppressWarnings("LineLength")
@ -76,7 +79,7 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
private static final String DEFAULT_NEW_REPO_TEXT = "https://"; private static final String DEFAULT_NEW_REPO_TEXT = "https://";
private enum AddRepoState { private enum AddRepoState {
DOESNT_EXIST, EXISTS_FINGERPRINT_MISMATCH, EXISTS_FINGERPRINT_MATCH, DOESNT_EXIST, EXISTS_FINGERPRINT_MISMATCH, EXISTS_ADD_MIRROR,
EXISTS_DISABLED, EXISTS_ENABLED, EXISTS_UPGRADABLE_TO_SIGNED, INVALID_URL, EXISTS_DISABLED, EXISTS_ENABLED, EXISTS_UPGRADABLE_TO_SIGNED, INVALID_URL,
IS_SWAP IS_SWAP
} }
@ -213,18 +216,35 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
private class AddRepo { private class AddRepo {
private final Context context; private final Context context;
private final HashMap<String, Repo> urlRepoMap = new HashMap<>();
private final HashMap<String, Repo> fingerprintRepoMap = new HashMap<>();
private final AlertDialog addRepoDialog; private final AlertDialog addRepoDialog;
private final TextView overwriteMessage; private final TextView overwriteMessage;
private final ColorStateList defaultTextColour; private final ColorStateList defaultTextColour;
private final Button addButton; private final Button addButton;
private AddRepoState addRepoState; private AddRepoState addRepoState;
/**
* Create new instance, setup GUI, and build maps for quickly looking
* up repos based on URL or fingerprint. These need to be in maps
* since the user input is validated as they are typing. This also
* checks that the repo type matches, e.g. "repo" or "archive".
*/
AddRepo(String newAddress, String newFingerprint, final String username, final String password) { AddRepo(String newAddress, String newFingerprint, final String username, final String password) {
context = ManageReposActivity.this; context = ManageReposActivity.this;
for (Repo repo : RepoProvider.Helper.all(context)) {
urlRepoMap.put(repo.address, repo);
for (String url : repo.getMirrorList()) {
urlRepoMap.put(url, repo);
}
if (TextUtils.equals(getRepoType(newAddress), getRepoType(repo.address))) {
fingerprintRepoMap.put(repo.fingerprint, repo);
}
}
final View view = getLayoutInflater().inflate(R.layout.addrepo, null); final View view = getLayoutInflater().inflate(R.layout.addrepo, null);
addRepoDialog = new AlertDialog.Builder(context).setView(view).create(); addRepoDialog = new AlertDialog.Builder(context).setView(view).create();
final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri); final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
@ -297,7 +317,7 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
case EXISTS_DISABLED: case EXISTS_DISABLED:
case EXISTS_UPGRADABLE_TO_SIGNED: case EXISTS_UPGRADABLE_TO_SIGNED:
case EXISTS_FINGERPRINT_MATCH: case EXISTS_ADD_MIRROR:
updateAndEnableExistingRepo(url, fp); updateAndEnableExistingRepo(url, fp);
finishedAddingRepo(); finishedAddingRepo();
break; break;
@ -347,9 +367,31 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
validateRepoDetails(newAddress == null ? "" : newAddress, newFingerprint == null ? "" : newFingerprint); validateRepoDetails(newAddress == null ? "" : newAddress, newFingerprint == null ? "" : newFingerprint);
} }
/**
* Gets the repo type as represented by the final segment of the path. This is
* a bit trickier with {@code content://} URLs, since they might have
* encoded "/" chars in it, for example:
* {@code content://authority/tree/313E-1F1C%3A/document/313E-1F1C%3Aguardianproject.info%2Ffdroid%2Frepo}
*/
private String getRepoType(String url) {
String last = Uri.parse(url).getLastPathSegment();
if (last == null) {
return "";
} else {
return new File(last).getName();
}
}
/** /**
* Compare the repo and the fingerprint against existing repositories, to see if this * Compare the repo and the fingerprint against existing repositories, to see if this
* repo matches and display a relevant message to the user if that is the case. * repo matches and display a relevant message to the user if that is the case. There
* are many different cases to handle:
* <ul>
* <li> a signed repo with a {@link Repo#address URL} and fingerprint that matches
* <li> a signed repo with a matching fingerprint and URL that matches a mirror
* <li> a signed repo with a matching fingerprint, but the URL doesn't match any known mirror
* <li>an unsigned repo and no fingerprint was supplied
* </ul>
*/ */
private void validateRepoDetails(@NonNull String uri, @NonNull String fingerprint) { private void validateRepoDetails(@NonNull String uri, @NonNull String fingerprint) {
@ -361,7 +403,10 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
// to the user until they try to save the repo. // to the user until they try to save the repo.
} }
final Repo repo = !TextUtils.isEmpty(uri) ? RepoProvider.Helper.findByAddress(context, uri) : null; Repo repo = fingerprintRepoMap.get(fingerprint);
if (repo == null) {
repo = urlRepoMap.get(uri);
}
if (repo == null) { if (repo == null) {
repoDoesntExist(repo); repoDoesntExist(repo);
@ -373,9 +418,10 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
} else if (repo.fingerprint != null && !repo.fingerprint.equalsIgnoreCase(fingerprint)) { } else if (repo.fingerprint != null && !repo.fingerprint.equalsIgnoreCase(fingerprint)) {
repoFingerprintDoesntMatch(repo); repoFingerprintDoesntMatch(repo);
} else { } else {
// Could be either an unsigned repo, and no fingerprint was supplied, if (!TextUtils.equals(repo.address, uri)
// or it could be a signed repo with a matching fingerprint. && !repo.getMirrorList().contains(uri)) {
if (repo.inuse) { repoExistsAddMirror(repo);
} else if (repo.inuse) {
repoExistsAndEnabled(repo); repoExistsAndEnabled(repo);
} else { } else {
repoExistsAndDisabled(repo); repoExistsAndDisabled(repo);
@ -659,11 +705,34 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
} }
Utils.debugLog(TAG, "Enabling existing repo: " + url); Utils.debugLog(TAG, "Enabling existing repo: " + url);
Repo repo = RepoProvider.Helper.findByAddress(context, url); Repo repo = fingerprintRepoMap.get(fingerprint);
if (repo == null) {
repo = RepoProvider.Helper.findByAddress(context, url);
}
ContentValues values = new ContentValues(2); ContentValues values = new ContentValues(2);
values.put(RepoTable.Cols.IN_USE, 1); values.put(RepoTable.Cols.IN_USE, 1);
values.put(RepoTable.Cols.FINGERPRINT, fingerprint); values.put(RepoTable.Cols.FINGERPRINT, fingerprint);
if (!TextUtils.equals(url, repo.address)) {
boolean addUserMirror = true;
for (String mirror : repo.getMirrorList()) {
if (TextUtils.equals(mirror, url)) {
addUserMirror = false;
}
}
if (addUserMirror) {
if (repo.userMirrors == null) {
repo.userMirrors = new String[]{url};
} else {
int last = repo.userMirrors.length;
repo.userMirrors = Arrays.copyOf(repo.userMirrors, last);
repo.userMirrors[last] = url;
}
values.put(RepoTable.Cols.USER_MIRRORS, Utils.serializeCommaSeparatedString(repo.userMirrors));
}
}
RepoProvider.Helper.update(context, repo, values); RepoProvider.Helper.update(context, repo, values);
notifyDataSetChanged(); notifyDataSetChanged();
finishedAddingRepo(); finishedAddingRepo();
} }

View File

@ -109,6 +109,7 @@ public class RepoDetailsActivity extends ActionBarActivity {
RepoTable.Cols.ADDRESS, RepoTable.Cols.ADDRESS,
RepoTable.Cols.FINGERPRINT, RepoTable.Cols.FINGERPRINT,
RepoTable.Cols.MIRRORS, RepoTable.Cols.MIRRORS,
RepoTable.Cols.USER_MIRRORS,
}; };
repo = RepoProvider.Helper.findById(this, repoId, projection); repo = RepoProvider.Helper.findById(this, repoId, projection);
@ -340,6 +341,19 @@ public class RepoDetailsActivity extends ActionBarActivity {
} }
officialMirrorsText.setText(builder.toString()); officialMirrorsText.setText(builder.toString());
} }
if (repo.userMirrors != null) {
TextView userMirrorsLabel = (TextView) repoView.findViewById(R.id.label_user_mirrors);
userMirrorsLabel.setVisibility(View.VISIBLE);
TextView userMirrorsText = (TextView) repoView.findViewById(R.id.text_user_mirrors);
userMirrorsText.setVisibility(View.VISIBLE);
StringBuilder builder = new StringBuilder();
for (String url : repo.userMirrors) {
builder.append("");
builder.append(url);
builder.append('\n');
}
userMirrorsText.setText(builder.toString());
}
name.setText(repo.name); name.setText(repo.name);

View File

@ -116,6 +116,7 @@ This often occurs with apps installed via Google Play or other sources, if they
<string name="no">No</string> <string name="no">No</string>
<string name="repo_add_title">Add new repository</string> <string name="repo_add_title">Add new repository</string>
<string name="repo_add_add">Add</string> <string name="repo_add_add">Add</string>
<string name="repo_add_mirror">Add mirror</string>
<string name="links">Links</string> <string name="links">Links</string>
<string name="versions">Versions</string> <string name="versions">Versions</string>
<string name="more">More</string> <string name="more">More</string>
@ -138,6 +139,7 @@ This often occurs with apps installed via Google Play or other sources, if they
<string name="repo_exists_enable">%1$s is already setup, confirm that you want to re-enable it.</string> <string name="repo_exists_enable">%1$s is already setup, confirm that you want to re-enable it.</string>
<string name="repo_exists_and_enabled">%1$s is already setup and enabled.</string> <string name="repo_exists_and_enabled">%1$s is already setup and enabled.</string>
<string name="repo_delete_to_overwrite">First delete %1$s in order to add this with a conflicting key.</string> <string name="repo_delete_to_overwrite">First delete %1$s in order to add this with a conflicting key.</string>
<string name="repo_exists_add_mirror">This is a copy of %1$s, add it as a mirror?</string>
<string name="bad_fingerprint">Bad fingerprint</string> <string name="bad_fingerprint">Bad fingerprint</string>
<string name="invalid_url">This is not a valid URL.</string> <string name="invalid_url">This is not a valid URL.</string>
<string name="malformed_repo_uri">Ignoring malformed repo URI: %s</string> <string name="malformed_repo_uri">Ignoring malformed repo URI: %s</string>