RepoDetails: add switches to disable any or all mirrors

This also needs to handle mirror lists with 1 element, since mirrors can
now be disabled. If the user disables all mirrors, then there will be only one URL in the
list of mirrors.  Asking for a random mirror in that case should not return
null, but the one enabled mirror.


closes #1696
This commit is contained in:
Hans-Christoph Steiner 2019-02-20 21:10:34 +01:00
parent a81a61be61
commit c3ec29df93
4 changed files with 105 additions and 22 deletions

View File

@ -97,6 +97,7 @@ public class DBHelper extends SQLiteOpenHelper {
+ RepoTable.Cols.ICON + " string, "
+ RepoTable.Cols.MIRRORS + " string, "
+ RepoTable.Cols.USER_MIRRORS + " string, "
+ RepoTable.Cols.DISABLED_MIRRORS + " string, "
+ RepoTable.Cols.PUSH_REQUESTS + " integer not null default " + Repo.PUSH_REQUEST_IGNORE
+ ");";
@ -223,7 +224,7 @@ public class DBHelper extends SQLiteOpenHelper {
+ "primary key(" + ApkAntiFeatureJoinTable.Cols.APK_ID + ", " + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + ") "
+ " );";
protected static final int DB_VERSION = 79;
protected static final int DB_VERSION = 80;
private final Context context;
@ -448,6 +449,17 @@ public class DBHelper extends SQLiteOpenHelper {
addLiberapayID(db, oldVersion);
addUserMirrorsFields(db, oldVersion);
removeNotNullFromVersionName(db, oldVersion);
addDisabledMirrorsFields(db, oldVersion);
}
private void addDisabledMirrorsFields(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 80) {
return;
}
if (!columnExists(db, RepoTable.NAME, RepoTable.Cols.DISABLED_MIRRORS)) {
Utils.debugLog(TAG, "Adding " + RepoTable.Cols.DISABLED_MIRRORS + " field to " + RepoTable.NAME + " table in db.");
db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.DISABLED_MIRRORS + " string;");
}
}
private void removeNotNullFromVersionName(SQLiteDatabase db, int oldVersion) {

View File

@ -86,6 +86,9 @@ public class Repo extends ValueObject {
@JsonIgnore
public int pushRequests = PUSH_REQUEST_IGNORE;
/**
* The canonical URL of the repo.
*/
public String address;
public String name;
public String description;
@ -125,8 +128,15 @@ public class Repo extends ValueObject {
/**
* Mirrors added by the user, either by UI input or by attaching removeable storage
*/
@JsonIgnore
public String[] userMirrors;
/**
* Mirrors that have been manually disabled by the user.
*/
@JsonIgnore
public String[] disabledMirrors;
public Repo() {
}
@ -193,6 +203,9 @@ public class Repo extends ValueObject {
case Cols.USER_MIRRORS:
userMirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
break;
case Cols.DISABLED_MIRRORS:
disabledMirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
break;
case Cols.PUSH_REQUESTS:
pushRequests = cursor.getInt(i);
break;
@ -334,6 +347,10 @@ public class Repo extends ValueObject {
userMirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.USER_MIRRORS));
}
if (values.containsKey(Cols.DISABLED_MIRRORS)) {
disabledMirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.DISABLED_MIRRORS));
}
if (values.containsKey(Cols.PUSH_REQUESTS)) {
pushRequests = toInt(values.getAsInteger(Cols.PUSH_REQUESTS));
}
@ -345,8 +362,9 @@ public class Repo extends ValueObject {
* mirror list.
*/
public boolean hasMirrors() {
return (mirrors != null && mirrors.length > 1)
|| (userMirrors != null && userMirrors.length > 0);
List<String> mirrorList = getMirrorList();
int size = mirrorList.size();
return size > 1 || (size == 1 && !mirrorList.contains(address));
}
/**
@ -361,6 +379,9 @@ public class Repo extends ValueObject {
allMirrors.addAll(Arrays.asList(mirrors));
}
allMirrors.add(address);
if (disabledMirrors != null) {
allMirrors.removeAll(Arrays.asList(disabledMirrors));
}
return new ArrayList<>(allMirrors);
}
@ -382,9 +403,11 @@ public class Repo extends ValueObject {
/**
* Get a random mirror URL from the list of mirrors for this repo. It will
* remove the URL in {@code mirrorToSkip} from consideration before choosing
* which mirror to return.
* which mirror to return. {@link #getMirrorList()} returns a list of all
* known mirrors <b>minus</b> the mirrors that have been disabled by the
* user preference, e.g. {@link #disabledMirrors}.
* <p>
* The mirror logic assumes that it has a mirrors list with at least once
* The mirror logic assumes that it has a mirrors list with at least one
* 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.
@ -394,6 +417,8 @@ public class Repo extends ValueObject {
* update. That makes it possible to do the first index update via SD Card
* or USB OTG drive.
*
* @see #getMirrorList()
* @see #disabledMirrors
* @see FDroidApp#resetMirrorVars()
* @see FDroidApp#switchUrlToNewMirror(String, Repo)
* @see FDroidApp#getTimeout()
@ -403,7 +428,7 @@ public class Repo extends ValueObject {
mirrorToSkip = address;
}
List<String> shuffledMirrors = getMirrorList();
if (shuffledMirrors.size() > 1) {
if (shuffledMirrors.size() > 0) {
Collections.shuffle(shuffledMirrors);
for (String m : shuffledMirrors) {
// Return a non default, and not last used mirror
@ -419,6 +444,6 @@ public class Repo extends ValueObject {
}
}
}
return null; // In case we are out of mirrors.
return address; // In case we are out of mirrors.
}
}

View File

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

View File

@ -44,6 +44,8 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema.RepoTable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
public class RepoDetailsActivity extends AppCompatActivity {
@ -82,6 +84,8 @@ public class RepoDetailsActivity extends AppCompatActivity {
private View repoView;
private String shareUrl;
private MirrorAdapter adapterToNotify;
/**
* Help function to make switching between two view states easier.
* Perhaps there is a better way to do this. I recall that using Adobe
@ -109,25 +113,19 @@ public class RepoDetailsActivity extends AppCompatActivity {
repoView = findViewById(R.id.repo_view);
repoId = getIntent().getLongExtra(ARG_REPO_ID, 0);
final String[] projection = {
RepoTable.Cols.NAME,
RepoTable.Cols.ADDRESS,
RepoTable.Cols.FINGERPRINT,
RepoTable.Cols.MIRRORS,
RepoTable.Cols.USER_MIRRORS,
};
repo = RepoProvider.Helper.findById(this, repoId, projection);
repo = RepoProvider.Helper.findById(this, repoId);
TextView inputUrl = findViewById(R.id.input_repo_url);
inputUrl.setText(repo.address);
RecyclerView officialMirrorListView = findViewById(R.id.official_mirror_list);
officialMirrorListView.setLayoutManager(new LinearLayoutManager(this));
officialMirrorListView.setAdapter(new MirrorAdapter(repo.mirrors));
adapterToNotify = new MirrorAdapter(repo, repo.mirrors);
officialMirrorListView.setAdapter(adapterToNotify);
RecyclerView userMirrorListView = findViewById(R.id.user_mirror_list);
userMirrorListView.setLayoutManager(new LinearLayoutManager(this));
userMirrorListView.setAdapter(new MirrorAdapter(repo.userMirrors));
userMirrorListView.setAdapter(new MirrorAdapter(repo, repo.userMirrors));
if (repo.address.startsWith("content://")) {
// no need to show a QR Code, it is not shareable
@ -464,7 +462,8 @@ public class RepoDetailsActivity extends AppCompatActivity {
}
private class MirrorAdapter extends RecyclerView.Adapter<MirrorAdapter.MirrorViewHolder> {
private String[] mirrors;
private final Repo repo;
private final String[] mirrors;
class MirrorViewHolder extends RecyclerView.ViewHolder {
View view;
@ -475,7 +474,8 @@ public class RepoDetailsActivity extends AppCompatActivity {
}
}
MirrorAdapter(String[] mirrors) {
MirrorAdapter(Repo repo, String[] mirrors) {
this.repo = repo;
this.mirrors = mirrors;
}
@ -487,12 +487,57 @@ public class RepoDetailsActivity extends AppCompatActivity {
}
@Override
public void onBindViewHolder(@NonNull MirrorViewHolder holder, int position) {
public void onBindViewHolder(@NonNull MirrorViewHolder holder, final int position) {
TextView repoNameTextView = holder.view.findViewById(R.id.repo_name);
repoNameTextView.setText(mirrors[position]);
final String itemMirror = mirrors[position];
boolean enabled = true;
if (repo.disabledMirrors != null) {
for (String disabled : repo.disabledMirrors) {
if (TextUtils.equals(itemMirror, disabled)) {
enabled = false;
break;
}
}
}
CompoundButton switchView = holder.view.findViewById(R.id.repo_switch);
switchView.setChecked(true);
switchView.setChecked(enabled);
switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
HashSet<String> disabledMirrors;
if (repo.disabledMirrors == null) {
disabledMirrors = new HashSet<>(1);
} else {
disabledMirrors = new HashSet<>(Arrays.asList(repo.disabledMirrors));
}
if (isChecked) {
disabledMirrors.remove(itemMirror);
} else {
disabledMirrors.add(itemMirror);
}
int totalMirrors = (repo.mirrors == null ? 0 : repo.mirrors.length)
+ (repo.userMirrors == null ? 0 : repo.userMirrors.length);
if (disabledMirrors.size() == totalMirrors) {
// if all mirrors are disabled, re-enable canonical repo as mirror
disabledMirrors.remove(repo.address);
adapterToNotify.notifyItemChanged(0);
}
if (disabledMirrors.size() == 0) {
repo.disabledMirrors = null;
} else {
repo.disabledMirrors = disabledMirrors.toArray(new String[disabledMirrors.size()]);
}
final ContentValues values = new ContentValues(1);
values.put(RepoTable.Cols.DISABLED_MIRRORS,
Utils.serializeCommaSeparatedString(repo.disabledMirrors));
RepoProvider.Helper.update(RepoDetailsActivity.this, repo, values);
}
});
View repoUnverified = holder.view.findViewById(R.id.repo_unverified);
repoUnverified.setVisibility(View.GONE);