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:
parent
89e04cc078
commit
2f0cb30ad0
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user