Refactored Repo db access from DB class to ContentProvider.
The performance improvement from this will not be noticable (perhaps there isn't one), however it is part of the bigger plan to move all of the DB access to ContentProviders. This will make a big improvement to the startup time of the app, given we are currently loading all of the apps to populate the list of apps. It will come at the cost of some apparantly weird code convensions. Most notably, when loading data from a content provder, you only ask for the fields that you intend to use. As a result of my Helper class which converts results from the content providers cursor into Repo value objects, there is no guarantee that certain attributes will be available on the value object. E.g. if I load repos and only ask for "_ID" and "ADDRESS", then it is meaningless to ask the resulting Repo object for its "VERSION" (it wont be there), despite it being a perfectly legal attribute from the Java compilers perspective. Repo.id field has also been made private (sqlite is the only entity should be able to set id's), and made id a long (sqlite stores identifiers as longs rather than ints).
This commit is contained in:
parent
cad37b0d55
commit
f8893431fb
@ -33,6 +33,12 @@
|
||||
android:allowBackup="true"
|
||||
android:theme="@style/AppThemeDark"
|
||||
android:supportsRtl="false" >
|
||||
|
||||
<provider
|
||||
android:authorities="org.fdroid.fdroid.data"
|
||||
android:name="org.fdroid.fdroid.data.RepoProvider"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:name=".FDroid"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize" >
|
||||
|
@ -25,6 +25,8 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
@ -859,20 +861,13 @@ public class AppDetails extends ListActivity {
|
||||
// Install the version of this app denoted by 'app.curApk'.
|
||||
private void install() {
|
||||
|
||||
String ra = null;
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
DB.Repo repo = db.getRepo(app.curApk.repo);
|
||||
if (repo != null)
|
||||
ra = repo.address;
|
||||
} catch (Exception ex) {
|
||||
Log.d("FDroid", "Failed to get repo address - " + ex.getMessage());
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
if (ra == null)
|
||||
String [] projection = { RepoProvider.DataColumns.ADDRESS };
|
||||
Repo repo = RepoProvider.Helper.findById(
|
||||
getContentResolver(), app.curApk.repo, projection);
|
||||
if (repo == null || repo.address == null) {
|
||||
return;
|
||||
final String repoaddress = ra;
|
||||
}
|
||||
final String repoaddress = repo.address;
|
||||
|
||||
if (!app.curApk.compatible) {
|
||||
AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
|
||||
|
@ -55,6 +55,7 @@ import org.fdroid.fdroid.compat.Compatibility;
|
||||
import org.fdroid.fdroid.compat.ContextCompat;
|
||||
import org.fdroid.fdroid.compat.SupportedArchitectures;
|
||||
import org.fdroid.fdroid.data.DBHelper;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
|
||||
public class DB {
|
||||
|
||||
@ -290,7 +291,7 @@ public class DB {
|
||||
public String version;
|
||||
public int vercode;
|
||||
public int detail_size; // Size in bytes - 0 means we don't know!
|
||||
public int repo; // ID of the repo it comes from
|
||||
public long repo; // ID of the repo it comes from
|
||||
public String detail_hash;
|
||||
public String detail_hashType;
|
||||
public int minSdkVersion; // 0 if unknown
|
||||
@ -404,111 +405,9 @@ public class DB {
|
||||
}
|
||||
}
|
||||
|
||||
// The TABLE_REPO table stores the details of the repositories in use.
|
||||
public static final String TABLE_REPO = "fdroid_repo";
|
||||
|
||||
public static class Repo {
|
||||
public int id;
|
||||
public String address;
|
||||
public String name;
|
||||
public String description;
|
||||
public int version; // index version, i.e. what fdroidserver built it - 0 if not specified
|
||||
public boolean inuse;
|
||||
public int priority;
|
||||
public String pubkey; // null for an unsigned repo
|
||||
public String fingerprint; // always null for an unsigned repo
|
||||
public int maxage; // maximum age of index that will be accepted - 0 for any
|
||||
public String lastetag; // last etag we updated from, null forces update
|
||||
public Date lastUpdated;
|
||||
|
||||
/**
|
||||
* If we haven't run an update for this repo yet, then the name
|
||||
* will be unknown, in which case we will just take a guess at an
|
||||
* appropriate name based on the url (e.g. "fdroid.org/archive")
|
||||
*/
|
||||
public String getName() {
|
||||
if (name == null) {
|
||||
String tempName = null;
|
||||
try {
|
||||
URL url = new URL(address);
|
||||
tempName = url.getHost() + url.getPath();
|
||||
} catch (MalformedURLException e) {
|
||||
tempName = address;
|
||||
}
|
||||
return tempName;
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getNumberOfApps() {
|
||||
DB db = DB.getDB();
|
||||
int count = db.countAppsForRepo(id);
|
||||
DB.releaseDB();
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param application In order invalidate the list of apps, we require
|
||||
* a reference to the top level application.
|
||||
*/
|
||||
public void enable(FDroidApp application) {
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
List<DB.Repo> toEnable = new ArrayList<DB.Repo>(1);
|
||||
toEnable.add(this);
|
||||
db.enableRepos(toEnable);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
application.invalidateAllApps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param application See DB.Repo.enable(application)
|
||||
*/
|
||||
public void disable(FDroidApp application) {
|
||||
disableRemove(application, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param application See DB.Repo.enable(application)
|
||||
*/
|
||||
public void remove(FDroidApp application) {
|
||||
disableRemove(application, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param application See DB.Repo.enable(application)
|
||||
*/
|
||||
private void disableRemove(FDroidApp application, boolean removeAfterDisabling) {
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
List<DB.Repo> toDisable = new ArrayList<DB.Repo>(1);
|
||||
toDisable.add(this);
|
||||
db.doDisableRepos(toDisable, removeAfterDisabling);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
application.invalidateAllApps();
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return this.pubkey != null && this.pubkey.length() > 0;
|
||||
}
|
||||
|
||||
public boolean hasBeenUpdated() {
|
||||
return this.lastetag != null;
|
||||
}
|
||||
}
|
||||
|
||||
private int countAppsForRepo(int id) {
|
||||
public int countAppsForRepo(long id) {
|
||||
String[] selection = { "COUNT(distinct id)" };
|
||||
String[] selectionArgs = { Integer.toString(id) };
|
||||
String[] selectionArgs = { Long.toString(id) };
|
||||
Cursor result = db.query(
|
||||
TABLE_APK, selection, "repo = ?", selectionArgs, "repo", null, null);
|
||||
if (result.getCount() > 0) {
|
||||
@ -554,8 +453,7 @@ public class DB {
|
||||
|
||||
// The date format used for storing dates (e.g. lastupdated, added) in the
|
||||
// database.
|
||||
public static SimpleDateFormat dateFormat = new SimpleDateFormat(
|
||||
"yyyy-MM-dd", Locale.ENGLISH);
|
||||
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
||||
|
||||
private DB(Context ctx) {
|
||||
|
||||
@ -659,7 +557,7 @@ public class DB {
|
||||
|
||||
private static final String[] POPULATE_APK_COLS = new String[] { "hash", "hashType", "size", "permissions" };
|
||||
|
||||
private void populateApkDetails(Apk apk, int repo) {
|
||||
private void populateApkDetails(Apk apk, long repo) {
|
||||
if (repo == 0 || repo == apk.repo) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
@ -692,7 +590,7 @@ public class DB {
|
||||
// Populate the details for the given app, if necessary.
|
||||
// If 'apkrepo' is non-zero, only apks from that repo are
|
||||
// populated (this is used during the update process)
|
||||
public void populateDetails(App app, int apkRepo) {
|
||||
public void populateDetails(App app, long apkRepo) {
|
||||
if (!app.detail_Populated) {
|
||||
populateAppDetails(app);
|
||||
}
|
||||
@ -747,10 +645,10 @@ public class DB {
|
||||
app.curVercode = c.getInt(9);
|
||||
String sAdded = c.getString(10);
|
||||
app.added = (sAdded == null || sAdded.length() == 0) ? null
|
||||
: dateFormat.parse(sAdded);
|
||||
: DATE_FORMAT.parse(sAdded);
|
||||
String sLastUpdated = c.getString(11);
|
||||
app.lastUpdated = (sLastUpdated == null || sLastUpdated
|
||||
.length() == 0) ? null : dateFormat
|
||||
.length() == 0) ? null : DATE_FORMAT
|
||||
.parse(sLastUpdated);
|
||||
app.compatible = c.getInt(12) == 1;
|
||||
app.ignoreAllUpdates = c.getInt(13) == 1;
|
||||
@ -783,12 +681,16 @@ public class DB {
|
||||
Log.d("FDroid", "Read app data from database " + " (took "
|
||||
+ (System.currentTimeMillis() - startTime) + " ms)");
|
||||
|
||||
List<Repo> repos = getRepos();
|
||||
cols = new String[] { "id", "version", "vercode", "sig", "srcname",
|
||||
"apkName", "minSdkVersion", "added", "features", "nativecode",
|
||||
"compatible", "repo" };
|
||||
c = db.query(TABLE_APK, cols, null, null, null, null,
|
||||
"vercode desc");
|
||||
String query = "SELECT apk.id, apk.version, apk.vercode, apk.sig,"
|
||||
+ " apk.srcname, apk.apkName, apk.minSdkVersion, "
|
||||
+ " apk.added, apk.features, apk.nativecode, "
|
||||
+ " apk.compatible, apk.repo, repo.version, repo.address "
|
||||
+ " FROM " + TABLE_APK + " as apk "
|
||||
+ " LEFT JOIN " + DBHelper.TABLE_REPO + " as repo "
|
||||
+ " ON repo._id = apk.repo "
|
||||
+ " ORDER BY apk.vercode DESC";
|
||||
|
||||
c = db.rawQuery(query, null);
|
||||
c.moveToFirst();
|
||||
|
||||
DisplayMetrics metrics = mContext.getResources()
|
||||
@ -829,21 +731,19 @@ public class DB {
|
||||
apk.minSdkVersion = c.getInt(6);
|
||||
String sApkAdded = c.getString(7);
|
||||
apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null
|
||||
: dateFormat.parse(sApkAdded);
|
||||
: DATE_FORMAT.parse(sApkAdded);
|
||||
apk.features = CommaSeparatedList.make(c.getString(8));
|
||||
apk.nativecode = CommaSeparatedList.make(c.getString(9));
|
||||
apk.compatible = compatible;
|
||||
apk.repo = repoid;
|
||||
app.apks.add(apk);
|
||||
if (app.iconUrl == null && app.icon != null) {
|
||||
for (DB.Repo repo : repos) {
|
||||
if (repo.id != repoid) continue;
|
||||
if (repo.version >= 11) {
|
||||
app.iconUrl = repo.address + iconsDir + app.icon;
|
||||
int repoVersion = c.getInt(12);
|
||||
String repoAddress = c.getString(13);
|
||||
if (repoVersion >= 11) {
|
||||
app.iconUrl = repoAddress + iconsDir + app.icon;
|
||||
} else {
|
||||
app.iconUrl = repo.address + "/icons/" + app.icon;
|
||||
}
|
||||
break;
|
||||
app.iconUrl = repoAddress + "/icons/" + app.icon;
|
||||
}
|
||||
}
|
||||
c.moveToNext();
|
||||
@ -1153,10 +1053,10 @@ public class DB {
|
||||
values.put("dogecoinAddr", upapp.detail_dogecoinAddr);
|
||||
values.put("flattrID", upapp.detail_flattrID);
|
||||
values.put("added",
|
||||
upapp.added == null ? "" : dateFormat.format(upapp.added));
|
||||
upapp.added == null ? "" : DATE_FORMAT.format(upapp.added));
|
||||
values.put(
|
||||
"lastUpdated",
|
||||
upapp.added == null ? "" : dateFormat
|
||||
upapp.added == null ? "" : DATE_FORMAT
|
||||
.format(upapp.lastUpdated));
|
||||
values.put("curVersion", upapp.curVersion);
|
||||
values.put("curVercode", upapp.curVercode);
|
||||
@ -1201,7 +1101,7 @@ public class DB {
|
||||
values.put("apkName", upapk.apkName);
|
||||
values.put("minSdkVersion", upapk.minSdkVersion);
|
||||
values.put("added",
|
||||
upapk.added == null ? "" : dateFormat.format(upapk.added));
|
||||
upapk.added == null ? "" : DATE_FORMAT.format(upapk.added));
|
||||
values.put("permissions",
|
||||
CommaSeparatedList.str(upapk.detail_permissions));
|
||||
values.put("features", CommaSeparatedList.str(upapk.features));
|
||||
@ -1216,105 +1116,6 @@ public class DB {
|
||||
}
|
||||
}
|
||||
|
||||
// Get details of a repo, given the ID. Returns null if the repo
|
||||
// doesn't exist.
|
||||
public Repo getRepo(int id) {
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = db.query(TABLE_REPO, new String[] { "address", "name",
|
||||
"description", "version", "inuse", "priority", "pubkey",
|
||||
"fingerprint", "maxage", "lastetag", "lastUpdated" },
|
||||
"id = ?", new String[] { Integer.toString(id) }, null, null, null);
|
||||
if (!c.moveToFirst())
|
||||
return null;
|
||||
Repo repo = new Repo();
|
||||
repo.id = id;
|
||||
repo.address = c.getString(0);
|
||||
repo.name = c.getString(1);
|
||||
repo.description = c.getString(2);
|
||||
repo.version = c.getInt(3);
|
||||
repo.inuse = (c.getInt(4) == 1);
|
||||
repo.priority = c.getInt(5);
|
||||
repo.pubkey = c.getString(6);
|
||||
repo.fingerprint = c.getString(7);
|
||||
repo.maxage = c.getInt(8);
|
||||
repo.lastetag = c.getString(9);
|
||||
try {
|
||||
repo.lastUpdated = c.getString(10) != null ?
|
||||
dateFormat.parse( c.getString(10)) :
|
||||
null;
|
||||
} catch (ParseException e) {
|
||||
Log.e("FDroid", "Error parsing date " + c.getString(10));
|
||||
}
|
||||
return repo;
|
||||
} finally {
|
||||
if (c != null)
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of the configured repositories.
|
||||
public List<Repo> getRepos() {
|
||||
List<Repo> repos = new ArrayList<Repo>();
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = db.query(TABLE_REPO, new String[] { "id", "address", "name",
|
||||
"description", "version", "inuse", "priority", "pubkey",
|
||||
"fingerprint", "maxage", "lastetag" },
|
||||
null, null, null, null, "priority");
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
Repo repo = new Repo();
|
||||
repo.id = c.getInt(0);
|
||||
repo.address = c.getString(1);
|
||||
repo.name = c.getString(2);
|
||||
repo.description = c.getString(3);
|
||||
repo.version = c.getInt(4);
|
||||
repo.inuse = (c.getInt(5) == 1);
|
||||
repo.priority = c.getInt(6);
|
||||
repo.pubkey = c.getString(7);
|
||||
repo.fingerprint = c.getString(8);
|
||||
repo.maxage = c.getInt(9);
|
||||
repo.lastetag = c.getString(10);
|
||||
repos.add(repo);
|
||||
c.moveToNext();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
return repos;
|
||||
}
|
||||
|
||||
public void enableRepos(List<DB.Repo> repos) {
|
||||
if (repos.isEmpty()) return;
|
||||
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put("inuse", 1);
|
||||
|
||||
String[] whereArgs = new String[repos.size()];
|
||||
StringBuilder where = new StringBuilder("address IN (");
|
||||
for (int i = 0; i < repos.size(); i ++) {
|
||||
Repo repo = repos.get(i);
|
||||
repo.inuse = true;
|
||||
whereArgs[i] = repo.address;
|
||||
where.append('?');
|
||||
if ( i < repos.size() - 1 ) {
|
||||
where.append(',');
|
||||
}
|
||||
}
|
||||
where.append(")");
|
||||
db.update(TABLE_REPO, values, where.toString(), whereArgs);
|
||||
}
|
||||
|
||||
public void changeServerStatus(String address) {
|
||||
db.execSQL("update " + TABLE_REPO
|
||||
+ " set inuse=1-inuse, lastetag=null where address = ?",
|
||||
new String[] { address });
|
||||
}
|
||||
|
||||
public void setIgnoreUpdates(String appid, boolean All, int This) {
|
||||
db.execSQL("update " + TABLE_APP + " set"
|
||||
+ " ignoreAllUpdates=" + (All ? '1' : '0')
|
||||
@ -1322,120 +1123,11 @@ public class DB {
|
||||
+ " where id = ?", new String[] { appid });
|
||||
}
|
||||
|
||||
public void updateRepoByAddress(Repo repo) {
|
||||
updateRepo(repo, "address", repo.address);
|
||||
}
|
||||
|
||||
public void updateRepo(Repo repo) {
|
||||
updateRepo(repo, "id", repo.id + "");
|
||||
}
|
||||
|
||||
private void updateRepo(Repo repo, String field, String value) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("name", repo.name);
|
||||
values.put("address", repo.address);
|
||||
values.put("description", repo.description);
|
||||
values.put("version", repo.version);
|
||||
values.put("inuse", repo.inuse);
|
||||
values.put("priority", repo.priority);
|
||||
values.put("pubkey", repo.pubkey);
|
||||
if (repo.pubkey != null && repo.fingerprint == null) {
|
||||
// we got a new pubkey, so calc the fingerprint
|
||||
values.put("fingerprint", DB.calcFingerprint(repo.pubkey));
|
||||
} else {
|
||||
values.put("fingerprint", repo.fingerprint);
|
||||
}
|
||||
values.put("maxage", repo.maxage);
|
||||
values.put("lastetag", (String) null);
|
||||
db.update(TABLE_REPO, values, field + " = ?",
|
||||
new String[] { value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the lastUpdated time for every enabled repo.
|
||||
*/
|
||||
public void refreshLastUpdates() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("lastUpdated", dateFormat.format(new Date()));
|
||||
db.update(TABLE_REPO, values, "inuse = 1",
|
||||
new String[] {});
|
||||
}
|
||||
|
||||
public void writeLastEtag(Repo repo) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("lastetag", repo.lastetag);
|
||||
values.put("lastUpdated", dateFormat.format(new Date()));
|
||||
db.update(TABLE_REPO, values, "address = ?",
|
||||
new String[] { repo.address });
|
||||
}
|
||||
|
||||
public void addRepo(String address, String name, String description,
|
||||
int version, int priority, String pubkey, String fingerprint,
|
||||
int maxage, boolean inuse)
|
||||
throws SecurityException {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("address", address);
|
||||
values.put("name", name);
|
||||
values.put("description", description);
|
||||
values.put("version", version);
|
||||
values.put("inuse", inuse ? 1 : 0);
|
||||
values.put("priority", priority);
|
||||
values.put("pubkey", pubkey);
|
||||
String calcedFingerprint = DB.calcFingerprint(pubkey);
|
||||
if (fingerprint == null) {
|
||||
fingerprint = calcedFingerprint;
|
||||
} else if (calcedFingerprint != null) {
|
||||
fingerprint = fingerprint.toUpperCase(Locale.ENGLISH);
|
||||
if (!fingerprint.equals(calcedFingerprint)) {
|
||||
throw new SecurityException("Given fingerprint does not match calculated one! ("
|
||||
+ fingerprint + " != " + calcedFingerprint);
|
||||
}
|
||||
}
|
||||
values.put("fingerprint", fingerprint);
|
||||
values.put("maxage", maxage);
|
||||
values.put("lastetag", (String) null);
|
||||
db.insert(TABLE_REPO, null, values);
|
||||
}
|
||||
|
||||
public void doDisableRepos(List<Repo> repos, boolean remove) {
|
||||
if (repos.isEmpty()) return;
|
||||
public void purgeApps(Repo repo, FDroidApp fdroid) {
|
||||
db.beginTransaction();
|
||||
|
||||
// TODO: Replace with
|
||||
// "delete from apk join repo where repo in (?, ?, ...)
|
||||
// "update repo set inuse = 0 | delete from repo ] where repo in (?, ?, ...)
|
||||
try {
|
||||
for (Repo repo : repos) {
|
||||
|
||||
String address = repo.address;
|
||||
// Before removing the repo, remove any apks that are
|
||||
// connected to it...
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = db.query(TABLE_REPO, new String[]{"id"},
|
||||
"address = ?", new String[]{address},
|
||||
null, null, null, null);
|
||||
c.moveToFirst();
|
||||
if (!c.isAfterLast()) {
|
||||
db.delete(TABLE_APK, "repo = ?",
|
||||
new String[] { Integer.toString(c.getInt(0)) });
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
if (remove)
|
||||
db.delete(TABLE_REPO, "address = ?",
|
||||
new String[] { address });
|
||||
else {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put("inuse", 0);
|
||||
values.put("lastetag", (String)null);
|
||||
db.update(TABLE_REPO, values, "address = ?",
|
||||
new String[] { address });
|
||||
}
|
||||
}
|
||||
db.delete(TABLE_APK, "repo = ?", new String[] { Long.toString(repo.getId()) });
|
||||
List<App> apps = getApps(false);
|
||||
for (App app : apps) {
|
||||
if (app.apks.isEmpty()) {
|
||||
@ -1446,6 +1138,8 @@ public class DB {
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
fdroid.invalidateAllApps();
|
||||
}
|
||||
|
||||
public int getSynchronizationMode() {
|
||||
|
@ -21,34 +21,39 @@ package org.fdroid.fdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.content.ContentValues;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.*;
|
||||
import org.fdroid.fdroid.DB.Repo;
|
||||
|
||||
import org.fdroid.fdroid.compat.ActionBarCompat;
|
||||
import org.fdroid.fdroid.compat.ClipboardCompat;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.views.RepoAdapter;
|
||||
import org.fdroid.fdroid.views.RepoDetailsActivity;
|
||||
import org.fdroid.fdroid.views.fragments.RepoDetailsFragment;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ManageRepo extends ListActivity {
|
||||
|
||||
private static final String DEFAULT_NEW_REPO_TEXT = "https://";
|
||||
private final int ADD_REPO = 1;
|
||||
private final int UPDATE_REPOS = 2;
|
||||
public class ManageRepo extends FragmentActivity {
|
||||
|
||||
/**
|
||||
* If we have a new repo added, or the address of a repo has changed, then
|
||||
@ -58,6 +63,122 @@ public class ManageRepo extends ListActivity {
|
||||
*/
|
||||
public static final String REQUEST_UPDATE = "update";
|
||||
|
||||
private RepoListFragment listFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
((FDroidApp) getApplication()).applyTheme(this);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
listFragment = new RepoListFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, listFragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
Intent ret = new Intent();
|
||||
if (listFragment.hasChanged()) {
|
||||
Log.i("FDroid", "Repo details have changed, prompting for update.");
|
||||
ret.putExtra(REQUEST_UPDATE, true);
|
||||
}
|
||||
setResult(Activity.RESULT_OK, ret);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RepoListFragment extends ListFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>,RepoAdapter.EnabledListener {
|
||||
|
||||
private static final String DEFAULT_NEW_REPO_TEXT = "https://";
|
||||
private final int ADD_REPO = 1;
|
||||
private final int UPDATE_REPOS = 2;
|
||||
|
||||
public boolean hasChanged() {
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
Uri uri = RepoProvider.getContentUri();
|
||||
Log.i("FDroid", "Creating repo loader '" + uri + "'.");
|
||||
String[] projection = new String[] {
|
||||
RepoProvider.DataColumns._ID,
|
||||
RepoProvider.DataColumns.NAME,
|
||||
RepoProvider.DataColumns.PUBLIC_KEY,
|
||||
RepoProvider.DataColumns.FINGERPRINT,
|
||||
RepoProvider.DataColumns.IN_USE
|
||||
};
|
||||
return new CursorLoader(getActivity(), uri, projection, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
Log.i("FDroid", "Repo cursor loaded.");
|
||||
repoAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||
Log.i("FDroid", "Repo cursor reset.");
|
||||
repoAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOTE: If somebody toggles a repo off then on again, it will have removed
|
||||
* all apps from the index when it was toggled off, so when it is toggled on
|
||||
* again, then it will require a refresh.
|
||||
*
|
||||
* Previously, I toyed with the idea of remembering whether they had
|
||||
* toggled on or off, and then only actually performing the function when
|
||||
* the activity stopped, but I think that will be problematic. What about
|
||||
* when they press the home button, or edit a repos details? It will start
|
||||
* to become somewhat-random as to when the actual enabling, disabling is
|
||||
* performed.
|
||||
*
|
||||
* So now, it just does the disable as soon as the user clicks "Off" and
|
||||
* then removes the apps. To compensate for the removal of apps from
|
||||
* index, it notifies the user via a toast that the apps have been removed.
|
||||
* Also, as before, it will still prompt the user to update the repos if
|
||||
* you toggled on on.
|
||||
*/
|
||||
@Override
|
||||
public void onSetEnabled(Repo repo, boolean isEnabled) {
|
||||
if (repo.inuse != isEnabled ) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, isEnabled ? 1 : 0);
|
||||
RepoProvider.Helper.update(
|
||||
getActivity().getContentResolver(), repo, values);
|
||||
|
||||
if (isEnabled) {
|
||||
changed = true;
|
||||
} else {
|
||||
FDroidApp app = (FDroidApp)getActivity().getApplication();
|
||||
RepoProvider.Helper.purgeApps(repo, app);
|
||||
String notification = getString(R.string.repo_disabled_notification, repo.name);
|
||||
Toast.makeText(getActivity(), notification, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PositiveAction {
|
||||
ADD_NEW, ENABLE, IGNORE
|
||||
}
|
||||
@ -74,16 +195,14 @@ public class ManageRepo extends ListActivity {
|
||||
private boolean isImportingRepo = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
((FDroidApp) getApplication()).applyTheme(this);
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActionBarCompat abCompat = ActionBarCompat.create(this);
|
||||
abCompat.setDisplayHomeAsUpEnabled(true);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
repoAdapter = new RepoAdapter(this);
|
||||
repoAdapter = new RepoAdapter(getActivity(), null);
|
||||
repoAdapter.setEnabledListener(this);
|
||||
setListAdapter(repoAdapter);
|
||||
|
||||
/*
|
||||
@ -106,7 +225,7 @@ public class ManageRepo extends ListActivity {
|
||||
*/
|
||||
|
||||
/* let's see if someone is trying to send us a new repo */
|
||||
Intent intent = getIntent();
|
||||
Intent intent = getActivity().getIntent();
|
||||
/* an URL from a click or a QRCode scan */
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
@ -139,27 +258,25 @@ public class ManageRepo extends ListActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refreshList();
|
||||
|
||||
//Starts a new or restarts an existing Loader in this manager
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
|
||||
super.onListItemClick(l, v, position, id);
|
||||
|
||||
DB.Repo repo = (DB.Repo)getListView().getItemAtPosition(position);
|
||||
Repo repo = new Repo((Cursor)getListView().getItemAtPosition(position));
|
||||
editRepo(repo);
|
||||
}
|
||||
|
||||
private void refreshList() {
|
||||
repoAdapter.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
MenuItem updateItem = menu.add(Menu.NONE, UPDATE_REPOS, 1,
|
||||
R.string.menu_update_repo).setIcon(R.drawable.ic_menu_refresh);
|
||||
@ -172,101 +289,22 @@ public class ManageRepo extends ListActivity {
|
||||
MenuItemCompat.setShowAsAction(addItem,
|
||||
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final int SHOW_REPO_DETAILS = 1;
|
||||
|
||||
public void editRepo(DB.Repo repo) {
|
||||
Log.d("FDroid", "Showing details screen for repo: '" + repo + "'.");
|
||||
Intent intent = new Intent(this, RepoDetailsActivity.class);
|
||||
intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.id);
|
||||
public void editRepo(Repo repo) {
|
||||
Intent intent = new Intent(getActivity(), RepoDetailsActivity.class);
|
||||
intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.getId());
|
||||
startActivityForResult(intent, SHOW_REPO_DETAILS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
|
||||
if (requestCode == SHOW_REPO_DETAILS && resultCode == RESULT_OK) {
|
||||
|
||||
boolean wasDeleted = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_DELETED, false);
|
||||
boolean wasEnabled = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_ENABLED, false);
|
||||
boolean wasDisabled = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_DISABLED, false);
|
||||
boolean wasChanged = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_CHANGED, false);
|
||||
|
||||
if (wasDeleted) {
|
||||
int repoId = data.getIntExtra(RepoDetailsActivity.DATA_REPO_ID, 0);
|
||||
remove(repoId);
|
||||
} else if (wasEnabled || wasDisabled || wasChanged) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DB.Repo getRepoById(int repoId) {
|
||||
for (int i = 0; i < getListAdapter().getCount(); i ++) {
|
||||
DB.Repo repo = (DB.Repo)getListAdapter().getItem(i);
|
||||
if (repo.id == repoId) {
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void remove(int repoId) {
|
||||
DB.Repo repo = getRepoById(repoId);
|
||||
if (repo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<DB.Repo> reposToRemove = new ArrayList<DB.Repo>(1);
|
||||
reposToRemove.add(repo);
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
db.doDisableRepos(reposToRemove, true);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
refreshList();
|
||||
}
|
||||
|
||||
protected List<Repo> getRepos() {
|
||||
List<Repo> repos = null;
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
repos = db.getRepos();
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
return repos;
|
||||
}
|
||||
|
||||
protected Repo getRepoByAddress(String address, List<Repo> repos) {
|
||||
if (address != null)
|
||||
for (Repo repo : repos)
|
||||
if (address.equals(repo.address))
|
||||
return repo;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void updateRepos() {
|
||||
UpdateService.updateNow(this).setListener(new ProgressListener() {
|
||||
UpdateService.updateNow(getActivity()).setListener(new ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
// No need to prompt to update any more, we just did it!
|
||||
changed = false;
|
||||
refreshList();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -276,14 +314,14 @@ public class ManageRepo extends ListActivity {
|
||||
}
|
||||
|
||||
private void showAddRepo(String newAddress, String newFingerprint) {
|
||||
|
||||
View view = getLayoutInflater().inflate(R.layout.addrepo, null);
|
||||
final AlertDialog alrt = new AlertDialog.Builder(this).setView(view).create();
|
||||
View view = getLayoutInflater(null).inflate(R.layout.addrepo, null);
|
||||
final AlertDialog alrt = new AlertDialog.Builder(getActivity()).setView(view).create();
|
||||
final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
|
||||
final EditText fingerprintEditText = (EditText) view.findViewById(R.id.edit_fingerprint);
|
||||
|
||||
List<Repo> repos = getRepos();
|
||||
final Repo repo = newAddress != null && isImportingRepo ? getRepoByAddress(newAddress, repos) : null;
|
||||
final Repo repo = ( newAddress != null && isImportingRepo )
|
||||
? RepoProvider.Helper.findByAddress(getActivity().getContentResolver(), newAddress)
|
||||
: null;
|
||||
|
||||
alrt.setIcon(android.R.drawable.ic_menu_add);
|
||||
alrt.setTitle(getString(R.string.repo_add_title));
|
||||
@ -312,8 +350,8 @@ public class ManageRepo extends ListActivity {
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
});
|
||||
@ -338,7 +376,7 @@ public class ManageRepo extends ListActivity {
|
||||
// this entry already exists and is not enabled, offer to enable it
|
||||
if (repo.inuse) {
|
||||
alrt.dismiss();
|
||||
Toast.makeText(this, R.string.repo_exists_and_enabled, Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(getActivity(), R.string.repo_exists_and_enabled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
} else {
|
||||
overwriteMessage.setText(R.string.repo_exists_enable);
|
||||
@ -373,12 +411,10 @@ public class ManageRepo extends ListActivity {
|
||||
* Adds a new repo to the database.
|
||||
*/
|
||||
private void createNewRepo(String address, String fingerprint) {
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
db.addRepo(address, null, null, 0, 10, null, fingerprint, 0, true);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, address);
|
||||
values.put(RepoProvider.DataColumns.FINGERPRINT, fingerprint);
|
||||
RepoProvider.Helper.insert(getActivity().getContentResolver(), values);
|
||||
finishedAddingRepo();
|
||||
}
|
||||
|
||||
@ -386,13 +422,10 @@ public class ManageRepo extends ListActivity {
|
||||
* Seeing as this repo already exists, we will force it to be enabled again.
|
||||
*/
|
||||
private void createNewRepo(Repo repo) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, 1);
|
||||
RepoProvider.Helper.update(getActivity().getContentResolver(), repo, values);
|
||||
repo.inuse = true;
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
db.updateRepoByAddress(repo);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
finishedAddingRepo();
|
||||
}
|
||||
|
||||
@ -404,17 +437,13 @@ public class ManageRepo extends ListActivity {
|
||||
private void finishedAddingRepo() {
|
||||
changed = true;
|
||||
if (isImportingRepo) {
|
||||
setResult(Activity.RESULT_OK);
|
||||
finish();
|
||||
} else {
|
||||
refreshList();
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
|
||||
super.onMenuItemSelected(featureId, item);
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
if (item.getItemId() == ADD_REPO) {
|
||||
showAddRepo();
|
||||
@ -424,7 +453,7 @@ public class ManageRepo extends ListActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -432,7 +461,7 @@ public class ManageRepo extends ListActivity {
|
||||
* Otherwise return "https://".
|
||||
*/
|
||||
private String getNewRepoUri() {
|
||||
ClipboardCompat clipboard = ClipboardCompat.create(this);
|
||||
ClipboardCompat clipboard = ClipboardCompat.create(getActivity());
|
||||
String text = clipboard.getText();
|
||||
if (text != null) {
|
||||
try {
|
||||
@ -447,46 +476,4 @@ public class ManageRepo extends ListActivity {
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
Intent ret = new Intent();
|
||||
if (changed) {
|
||||
Log.i("FDroid", "Repo details have changed, prompting for update.");
|
||||
ret.putExtra(REQUEST_UPDATE, true);
|
||||
}
|
||||
setResult(RESULT_OK, ret);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: If somebody toggles a repo off then on again, it will have removed
|
||||
* all apps from the index when it was toggled off, so when it is toggled on
|
||||
* again, then it will require a refresh.
|
||||
*
|
||||
* Previously, I toyed with the idea of remembering whether they had
|
||||
* toggled on or off, and then only actually performing the function when
|
||||
* the activity stopped, but I think that will be problematic. What about
|
||||
* when they press the home button, or edit a repos details? It will start
|
||||
* to become somewhat-random as to when the actual enabling, disabling is
|
||||
* performed.
|
||||
*
|
||||
* So now, it just does the disable as soon as the user clicks "Off" and
|
||||
* then removes the apps. To compensate for the removal of apps from
|
||||
* index, it notifies the user via a toast that the apps have been removed.
|
||||
* Also, as before, it will still prompt the user to update the repos if
|
||||
* you toggled on on.
|
||||
*/
|
||||
public void setRepoEnabled(DB.Repo repo, boolean enabled) {
|
||||
FDroidApp app = (FDroidApp)getApplication();
|
||||
if (enabled) {
|
||||
repo.enable(app);
|
||||
changed = true;
|
||||
} else {
|
||||
repo.disable(app);
|
||||
String notification = getString(R.string.repo_disabled_notification, repo.toString());
|
||||
Toast.makeText(this, notification, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.os.Bundle;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.updater.RepoUpdater;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
@ -33,7 +34,7 @@ import java.util.Map;
|
||||
public class RepoXMLHandler extends DefaultHandler {
|
||||
|
||||
// The repo we're processing.
|
||||
private DB.Repo repo;
|
||||
private Repo repo;
|
||||
|
||||
private Map<String, DB.App> apps;
|
||||
private List<DB.App> appsList;
|
||||
@ -62,7 +63,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
|
||||
private int totalAppCount;
|
||||
|
||||
public RepoXMLHandler(DB.Repo repo, List<DB.App> appsList, ProgressListener listener) {
|
||||
public RepoXMLHandler(Repo repo, List<DB.App> appsList, ProgressListener listener) {
|
||||
this.repo = repo;
|
||||
this.apps = new HashMap<String, DB.App>();
|
||||
for (DB.App app : appsList) this.apps.put(app.id, app);
|
||||
@ -157,7 +158,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
}
|
||||
} else if (curel.equals("added")) {
|
||||
try {
|
||||
curapk.added = str.length() == 0 ? null : DB.dateFormat
|
||||
curapk.added = str.length() == 0 ? null : DB.DATE_FORMAT
|
||||
.parse(str);
|
||||
} catch (ParseException e) {
|
||||
curapk.added = null;
|
||||
@ -204,7 +205,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
curapp.detail_trackerURL = str;
|
||||
} else if (curel.equals("added")) {
|
||||
try {
|
||||
curapp.added = str.length() == 0 ? null : DB.dateFormat
|
||||
curapp.added = str.length() == 0 ? null : DB.DATE_FORMAT
|
||||
.parse(str);
|
||||
} catch (ParseException e) {
|
||||
curapp.added = null;
|
||||
@ -212,7 +213,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
} else if (curel.equals("lastupdated")) {
|
||||
try {
|
||||
curapp.lastUpdated = str.length() == 0 ? null
|
||||
: DB.dateFormat.parse(str);
|
||||
: DB.DATE_FORMAT.parse(str);
|
||||
} catch (ParseException e) {
|
||||
curapp.lastUpdated = null;
|
||||
}
|
||||
@ -281,7 +282,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
} else if (localName.equals("package") && curapp != null && curapk == null) {
|
||||
curapk = new DB.Apk();
|
||||
curapk.id = curapp.id;
|
||||
curapk.repo = repo.id;
|
||||
curapk.repo = repo.getId();
|
||||
hashType = null;
|
||||
|
||||
} else if (localName.equals("hash") && curapk != null) {
|
||||
|
@ -41,12 +41,16 @@ import android.os.Parcelable;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.updater.RepoUpdater;
|
||||
|
||||
public class UpdateService extends IntentService implements ProgressListener {
|
||||
@ -280,22 +284,23 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
// Grab some preliminary information, then we can release the
|
||||
// database while we do all the downloading, etc...
|
||||
int updates = 0;
|
||||
List<DB.Repo> repos;
|
||||
List<Repo> repos;
|
||||
List<DB.App> apps;
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
repos = db.getRepos();
|
||||
apps = db.getApps(false);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
|
||||
repos = RepoProvider.Helper.all(getContentResolver());
|
||||
|
||||
// Process each repo...
|
||||
List<DB.App> updatingApps = new ArrayList<DB.App>();
|
||||
Set<Integer> keeprepos = new TreeSet<Integer>();
|
||||
Set<Long> keeprepos = new TreeSet<Long>();
|
||||
boolean changes = false;
|
||||
boolean update;
|
||||
for (DB.Repo repo : repos) {
|
||||
for (Repo repo : repos) {
|
||||
if (!repo.inuse)
|
||||
continue;
|
||||
// are we updating all repos, or just one?
|
||||
@ -306,7 +311,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
if (address.equals(repo.address)) {
|
||||
update = true;
|
||||
} else {
|
||||
keeprepos.add(repo.id);
|
||||
keeprepos.add(repo.getId());
|
||||
update = false;
|
||||
}
|
||||
}
|
||||
@ -321,7 +326,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
updatingApps.addAll(updater.getApps());
|
||||
changes = true;
|
||||
} else {
|
||||
keeprepos.add(repo.id);
|
||||
keeprepos.add(repo.getId());
|
||||
}
|
||||
} catch (RepoUpdater.UpdateException e) {
|
||||
errmsg += (errmsg.length() == 0 ? "" : "\n") + e.getMessage();
|
||||
@ -343,7 +348,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
// Need to flag things we're keeping despite having received
|
||||
// no data about during the update. (i.e. stuff from a repo
|
||||
// that we know is unchanged due to the etag)
|
||||
for (int keep : keeprepos) {
|
||||
for (long keep : keeprepos) {
|
||||
for (DB.App app : apps) {
|
||||
boolean keepapp = false;
|
||||
for (DB.Apk apk : app.apks) {
|
||||
@ -378,8 +383,6 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
db.updateApplication(app);
|
||||
}
|
||||
db.endUpdate();
|
||||
for (DB.Repo repo : repos)
|
||||
db.writeLastEtag(repo);
|
||||
} catch (Exception ex) {
|
||||
db.cancelUpdate();
|
||||
Log.e("FDroid", "Exception during update processing:\n"
|
||||
|
@ -36,6 +36,7 @@ import java.util.Locale;
|
||||
import android.content.Context;
|
||||
|
||||
import com.nostra13.universalimageloader.utils.StorageUtils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
|
||||
public final class Utils {
|
||||
|
||||
@ -159,7 +160,7 @@ public final class Utils {
|
||||
return count;
|
||||
}
|
||||
|
||||
public static String formatFingerprint(DB.Repo repo) {
|
||||
public static String formatFingerprint(Repo repo) {
|
||||
return formatFingerprint(repo.pubkey);
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,27 @@
|
||||
package org.fdroid.fdroid.compat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.ToggleButton;
|
||||
import org.fdroid.fdroid.ManageRepo;
|
||||
|
||||
public abstract class SwitchCompat extends Compatibility {
|
||||
|
||||
protected final ManageRepo activity;
|
||||
protected final Context context;
|
||||
|
||||
protected SwitchCompat(ManageRepo activity) {
|
||||
this.activity = activity;
|
||||
protected SwitchCompat(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public abstract CompoundButton createSwitch();
|
||||
|
||||
public static SwitchCompat create(ManageRepo activity) {
|
||||
public static SwitchCompat create(Context context) {
|
||||
if (hasApi(14)) {
|
||||
return new IceCreamSwitch(activity);
|
||||
return new IceCreamSwitch(context);
|
||||
} else {
|
||||
return new OldSwitch(activity);
|
||||
return new OldSwitch(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,24 +30,24 @@ public abstract class SwitchCompat extends Compatibility {
|
||||
@TargetApi(14)
|
||||
class IceCreamSwitch extends SwitchCompat {
|
||||
|
||||
protected IceCreamSwitch(ManageRepo activity) {
|
||||
super(activity);
|
||||
protected IceCreamSwitch(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundButton createSwitch() {
|
||||
return new Switch(activity);
|
||||
return new Switch(context);
|
||||
}
|
||||
}
|
||||
|
||||
class OldSwitch extends SwitchCompat {
|
||||
|
||||
protected OldSwitch(ManageRepo activity) {
|
||||
super(activity);
|
||||
protected OldSwitch(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundButton createSwitch() {
|
||||
return new ToggleButton(activity);
|
||||
return new ToggleButton(context);
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,15 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
|
||||
public static final String DATABASE_NAME = "fdroid";
|
||||
|
||||
public static final String TABLE_REPO = "fdroid_repo";
|
||||
|
||||
private static final String CREATE_TABLE_REPO = "create table "
|
||||
+ DB.TABLE_REPO + " (id integer primary key, address text not null, "
|
||||
+ TABLE_REPO + " (_id integer primary key, "
|
||||
+ "address text not null, "
|
||||
+ "name text, description text, inuse integer not null, "
|
||||
+ "priority integer not null, pubkey text, fingerprint text, "
|
||||
+ "maxage integer not null default 0, version integer not null default 0,"
|
||||
+ "maxage integer not null default 0, "
|
||||
+ "version integer not null default 0, "
|
||||
+ "lastetag text, lastUpdated string);";
|
||||
|
||||
private static final String CREATE_TABLE_APK = "create table " + DB.TABLE_APK
|
||||
@ -49,7 +53,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
+ "ignoreThisUpdate int not null,"
|
||||
+ "primary key(id));";
|
||||
|
||||
private static final int DB_VERSION = 35;
|
||||
private static final int DB_VERSION = 37;
|
||||
|
||||
private Context context;
|
||||
|
||||
@ -58,6 +62,79 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private void populateRepoNames(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 37) {
|
||||
String[] columns = { "address", "_id" };
|
||||
Cursor cursor = db.query(TABLE_REPO, columns,
|
||||
"name IS NULL OR name = ''", null, null, null, null);
|
||||
cursor.moveToFirst();
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
String address = cursor.getString(0);
|
||||
long id = cursor.getInt(1);
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put("name", Repo.addressToName(address));
|
||||
String[] args = { Long.toString( id ) };
|
||||
db.update(TABLE_REPO, values, "_id = ?", args);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renameRepoId(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 36) {
|
||||
|
||||
Log.d("FDroid", "Renaming " + TABLE_REPO + ".id to _id");
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
// http://stackoverflow.com/questions/805363/how-do-i-rename-a-column-in-a-sqlite-database-table#805508
|
||||
String tempTableName = TABLE_REPO + "__temp__";
|
||||
db.execSQL("ALTER TABLE " + TABLE_REPO + " RENAME TO " + tempTableName + ";" );
|
||||
|
||||
// I realise this is available in the CREATE_TABLE_REPO above,
|
||||
// however I have a feeling that it will need to be the same as the
|
||||
// current structure of the table as of DBVersion 36, or else we may
|
||||
// get into strife. For example, if there was a field that
|
||||
// got removed, then it will break the "insert select"
|
||||
// statement. Therefore, I've put a copy of CREATE_TABLE_REPO
|
||||
// here that is the same as it was at DBVersion 36.
|
||||
String createTableDdl = "create table " + TABLE_REPO + " ("
|
||||
+ "_id integer not null primary key, "
|
||||
+ "address text not null, "
|
||||
+ "name text, "
|
||||
+ "description text, "
|
||||
+ "inuse integer not null, "
|
||||
+ "priority integer not null, "
|
||||
+ "pubkey text, "
|
||||
+ "fingerprint text, "
|
||||
+ "maxage integer not null default 0, "
|
||||
+ "version integer not null default 0, "
|
||||
+ "lastetag text, "
|
||||
+ "lastUpdated string);";
|
||||
|
||||
db.execSQL(createTableDdl);
|
||||
|
||||
String nonIdFields = "address, name, description, inuse, priority, " +
|
||||
"pubkey, fingerprint, maxage, version, lastetag, lastUpdated";
|
||||
|
||||
String insertSql = "INSERT INTO " + TABLE_REPO +
|
||||
"(_id, " + nonIdFields + " ) " +
|
||||
"SELECT id, " + nonIdFields + " FROM " + tempTableName + ";";
|
||||
|
||||
db.execSQL(insertSql);
|
||||
db.execSQL("DROP TABLE " + tempTableName + ";");
|
||||
db.setTransactionSuccessful();
|
||||
} catch (Exception e) {
|
||||
Log.e("FDroid", "Error renaming id to _id: " + e.getMessage());
|
||||
}
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
|
||||
@ -80,7 +157,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
values.put("inuse", 1);
|
||||
values.put("priority", 10);
|
||||
values.put("lastetag", (String) null);
|
||||
db.insert(DB.TABLE_REPO, null, values);
|
||||
db.insert(TABLE_REPO, null, values);
|
||||
|
||||
values = new ContentValues();
|
||||
values.put("address",
|
||||
@ -97,7 +174,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
values.put("inuse", 0);
|
||||
values.put("priority", 20);
|
||||
values.put("lastetag", (String) null);
|
||||
db.insert(DB.TABLE_REPO, null, values);
|
||||
db.insert(TABLE_REPO, null, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,6 +195,8 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
addMaxAgeToRepo(db, oldVersion);
|
||||
addVersionToRepo(db, oldVersion);
|
||||
addLastUpdatedToRepo(db, oldVersion);
|
||||
renameRepoId(db, oldVersion);
|
||||
populateRepoNames(db, oldVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,13 +205,13 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
*/
|
||||
private void migradeRepoTable(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 20) {
|
||||
List<DB.Repo> oldrepos = new ArrayList<DB.Repo>();
|
||||
Cursor c = db.query(DB.TABLE_REPO,
|
||||
List<Repo> oldrepos = new ArrayList<Repo>();
|
||||
Cursor c = db.query(TABLE_REPO,
|
||||
new String[] { "address", "inuse", "pubkey" },
|
||||
null, null, null, null, null);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
DB.Repo repo = new DB.Repo();
|
||||
Repo repo = new Repo();
|
||||
repo.address = c.getString(0);
|
||||
repo.inuse = (c.getInt(1) == 1);
|
||||
repo.pubkey = c.getString(2);
|
||||
@ -140,16 +219,16 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
c.moveToNext();
|
||||
}
|
||||
c.close();
|
||||
db.execSQL("drop table " + DB.TABLE_REPO);
|
||||
db.execSQL("drop table " + TABLE_REPO);
|
||||
db.execSQL(CREATE_TABLE_REPO);
|
||||
for (DB.Repo repo : oldrepos) {
|
||||
for (Repo repo : oldrepos) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("address", repo.address);
|
||||
values.put("inuse", repo.inuse);
|
||||
values.put("priority", 10);
|
||||
values.put("pubkey", repo.pubkey);
|
||||
values.put("lastetag", (String) null);
|
||||
db.insert(DB.TABLE_REPO, null, values);
|
||||
db.insert(TABLE_REPO, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,19 +239,19 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
*/
|
||||
private void addNameAndDescriptionToRepo(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 21) {
|
||||
if (!columnExists(db, DB.TABLE_REPO, "name"))
|
||||
db.execSQL("alter table " + DB.TABLE_REPO + " add column name text");
|
||||
if (!columnExists(db, DB.TABLE_REPO, "description"))
|
||||
db.execSQL("alter table " + DB.TABLE_REPO + " add column description text");
|
||||
if (!columnExists(db, TABLE_REPO, "name"))
|
||||
db.execSQL("alter table " + TABLE_REPO + " add column name text");
|
||||
if (!columnExists(db, TABLE_REPO, "description"))
|
||||
db.execSQL("alter table " + TABLE_REPO + " add column description text");
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("name", context.getString(R.string.default_repo_name));
|
||||
values.put("description", context.getString(R.string.default_repo_description));
|
||||
db.update(DB.TABLE_REPO, values, "address = ?", new String[]{
|
||||
db.update(TABLE_REPO, values, "address = ?", new String[]{
|
||||
context.getString(R.string.default_repo_address)});
|
||||
values.clear();
|
||||
values.put("name", context.getString(R.string.default_repo_name2));
|
||||
values.put("description", context.getString(R.string.default_repo_description2));
|
||||
db.update(DB.TABLE_REPO, values, "address = ?", new String[] {
|
||||
db.update(TABLE_REPO, values, "address = ?", new String[] {
|
||||
context.getString(R.string.default_repo_address2) });
|
||||
}
|
||||
|
||||
@ -184,44 +263,44 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
*/
|
||||
private void addFingerprintToRepo(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 29) {
|
||||
if (!columnExists(db, DB.TABLE_REPO, "fingerprint"))
|
||||
db.execSQL("alter table " + DB.TABLE_REPO + " add column fingerprint text");
|
||||
List<DB.Repo> oldrepos = new ArrayList<DB.Repo>();
|
||||
Cursor c = db.query(DB.TABLE_REPO,
|
||||
if (!columnExists(db, TABLE_REPO, "fingerprint"))
|
||||
db.execSQL("alter table " + TABLE_REPO + " add column fingerprint text");
|
||||
List<Repo> oldrepos = new ArrayList<Repo>();
|
||||
Cursor c = db.query(TABLE_REPO,
|
||||
new String[] { "address", "pubkey" },
|
||||
null, null, null, null, null);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
DB.Repo repo = new DB.Repo();
|
||||
Repo repo = new Repo();
|
||||
repo.address = c.getString(0);
|
||||
repo.pubkey = c.getString(1);
|
||||
oldrepos.add(repo);
|
||||
c.moveToNext();
|
||||
}
|
||||
c.close();
|
||||
for (DB.Repo repo : oldrepos) {
|
||||
for (Repo repo : oldrepos) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("fingerprint", DB.calcFingerprint(repo.pubkey));
|
||||
db.update(DB.TABLE_REPO, values, "address = ?", new String[] { repo.address });
|
||||
db.update(TABLE_REPO, values, "address = ?", new String[] { repo.address });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMaxAgeToRepo(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 30) {
|
||||
db.execSQL("alter table " + DB.TABLE_REPO + " add column maxage integer not null default 0");
|
||||
db.execSQL("alter table " + TABLE_REPO + " add column maxage integer not null default 0");
|
||||
}
|
||||
}
|
||||
|
||||
private void addVersionToRepo(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 33 && !columnExists(db, DB.TABLE_REPO, "version")) {
|
||||
db.execSQL("alter table " + DB.TABLE_REPO + " add column version integer not null default 0");
|
||||
if (oldVersion < 33 && !columnExists(db, TABLE_REPO, "version")) {
|
||||
db.execSQL("alter table " + TABLE_REPO + " add column version integer not null default 0");
|
||||
}
|
||||
}
|
||||
|
||||
private void addLastUpdatedToRepo(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion < 35 && !columnExists(db, DB.TABLE_REPO, "lastUpdated")) {
|
||||
db.execSQL("Alter table " + DB.TABLE_REPO + " add column lastUpdated string");
|
||||
if (oldVersion < 35 && !columnExists(db, TABLE_REPO, "lastUpdated")) {
|
||||
db.execSQL("Alter table " + TABLE_REPO + " add column lastUpdated string");
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,7 +309,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
.putBoolean("triedEmptyUpdate", false).commit();
|
||||
db.execSQL("drop table " + DB.TABLE_APP);
|
||||
db.execSQL("drop table " + DB.TABLE_APK);
|
||||
db.execSQL("update " + DB.TABLE_REPO + " set lastetag = NULL");
|
||||
db.execSQL("update " + TABLE_REPO + " set lastetag = NULL");
|
||||
createAppApk(db);
|
||||
}
|
||||
|
||||
|
59
src/org/fdroid/fdroid/data/FDroidProvider.java
Normal file
59
src/org/fdroid/fdroid/data/FDroidProvider.java
Normal file
@ -0,0 +1,59 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
|
||||
abstract class FDroidProvider extends ContentProvider {
|
||||
|
||||
public static final String AUTHORITY = "org.fdroid.fdroid.data";
|
||||
|
||||
protected static final int CODE_LIST = 1;
|
||||
protected static final int CODE_SINGLE = 2;
|
||||
|
||||
private DBHelper dbHelper;
|
||||
|
||||
abstract protected String getTableName();
|
||||
|
||||
abstract protected String getProviderName();
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
dbHelper = new DBHelper(getContext());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final DBHelper db() {
|
||||
return dbHelper;
|
||||
}
|
||||
|
||||
protected final SQLiteDatabase read() {
|
||||
return db().getReadableDatabase();
|
||||
}
|
||||
|
||||
protected final SQLiteDatabase write() {
|
||||
return db().getWritableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
String type;
|
||||
switch(getMatcher().match(uri)) {
|
||||
case CODE_LIST:
|
||||
type = "dir";
|
||||
break;
|
||||
|
||||
case CODE_SINGLE:
|
||||
default:
|
||||
type = "item";
|
||||
break;
|
||||
|
||||
}
|
||||
return "vnd.android.cursor." + type + "/vnd." + AUTHORITY + "." + getProviderName();
|
||||
}
|
||||
|
||||
abstract protected UriMatcher getMatcher();
|
||||
|
||||
}
|
||||
|
176
src/org/fdroid/fdroid/data/Repo.java
Normal file
176
src/org/fdroid/fdroid/data/Repo.java
Normal file
@ -0,0 +1,176 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
import org.fdroid.fdroid.DB;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
public class Repo {
|
||||
|
||||
private long id;
|
||||
|
||||
public String address;
|
||||
public String name;
|
||||
public String description;
|
||||
public int version; // index version, i.e. what fdroidserver built it - 0 if not specified
|
||||
public boolean inuse;
|
||||
public int priority;
|
||||
public String pubkey; // null for an unsigned repo
|
||||
public String fingerprint; // always null for an unsigned repo
|
||||
public int maxage; // maximum age of index that will be accepted - 0 for any
|
||||
public String lastetag; // last etag we updated from, null forces update
|
||||
public Date lastUpdated;
|
||||
|
||||
public Repo() {
|
||||
|
||||
}
|
||||
|
||||
public Repo(Cursor cursor) {
|
||||
for(int i = 0; i < cursor.getColumnCount(); i ++ ) {
|
||||
String column = cursor.getColumnName(i);
|
||||
if (column.equals(RepoProvider.DataColumns._ID)) {
|
||||
id = cursor.getInt(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.LAST_ETAG)) {
|
||||
lastetag = cursor.getString(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.ADDRESS)) {
|
||||
address = cursor.getString(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.DESCRIPTION)) {
|
||||
description = cursor.getString(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.FINGERPRINT)) {
|
||||
fingerprint = cursor.getString(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.IN_USE)) {
|
||||
inuse = cursor.getInt(i) == 1;
|
||||
} else if (column.equals(RepoProvider.DataColumns.LAST_UPDATED)) {
|
||||
String dateString = cursor.getString(i);
|
||||
if (dateString != null) {
|
||||
try {
|
||||
lastUpdated = DB.DATE_FORMAT.parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
Log.e("FDroid", "Error parsing date " + dateString);
|
||||
}
|
||||
}
|
||||
} else if (column.equals(RepoProvider.DataColumns.MAX_AGE)) {
|
||||
maxage = cursor.getInt(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.VERSION)) {
|
||||
version = cursor.getInt(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.NAME)) {
|
||||
name = cursor.getString(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.PUBLIC_KEY)) {
|
||||
pubkey = cursor.getString(i);
|
||||
} else if (column.equals(RepoProvider.DataColumns.PRIORITY)) {
|
||||
priority = cursor.getInt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getId() { return id; }
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getNumberOfApps() {
|
||||
DB db = DB.getDB();
|
||||
int count = db.countAppsForRepo(id);
|
||||
DB.releaseDB();
|
||||
return count;
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return this.pubkey != null && this.pubkey.length() > 0;
|
||||
}
|
||||
|
||||
public boolean hasBeenUpdated() {
|
||||
return this.lastetag != null;
|
||||
}
|
||||
/**
|
||||
* If we haven't run an update for this repo yet, then the name
|
||||
* will be unknown, in which case we will just take a guess at an
|
||||
* appropriate name based on the url (e.g. "fdroid.org/archive")
|
||||
*/
|
||||
public static String addressToName(String address) {
|
||||
String tempName;
|
||||
try {
|
||||
URL url = new URL(address);
|
||||
tempName = url.getHost() + url.getPath();
|
||||
} catch (MalformedURLException e) {
|
||||
tempName = address;
|
||||
}
|
||||
return tempName;
|
||||
}
|
||||
|
||||
private static int toInt(Integer value) {
|
||||
if (value == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public void setValues(ContentValues values) {
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns._ID)) {
|
||||
id = toInt(values.getAsInteger(RepoProvider.DataColumns._ID));
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.LAST_ETAG)) {
|
||||
lastetag = values.getAsString(RepoProvider.DataColumns.LAST_ETAG);
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.ADDRESS)) {
|
||||
address = values.getAsString(RepoProvider.DataColumns.ADDRESS);
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.DESCRIPTION)) {
|
||||
description = values.getAsString(RepoProvider.DataColumns.DESCRIPTION);
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.FINGERPRINT)) {
|
||||
fingerprint = values.getAsString(RepoProvider.DataColumns.FINGERPRINT);
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.IN_USE)) {
|
||||
inuse = toInt(values.getAsInteger(RepoProvider.DataColumns.FINGERPRINT)) == 1;
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.LAST_UPDATED)) {
|
||||
String dateString = values.getAsString(RepoProvider.DataColumns.LAST_UPDATED);
|
||||
if (dateString != null) {
|
||||
try {
|
||||
lastUpdated = DB.DATE_FORMAT.parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
Log.e("FDroid", "Error parsing date " + dateString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.MAX_AGE)) {
|
||||
maxage = toInt(values.getAsInteger(RepoProvider.DataColumns.MAX_AGE));
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.VERSION)) {
|
||||
version = toInt(values.getAsInteger(RepoProvider.DataColumns.VERSION));
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.NAME)) {
|
||||
name = values.getAsString(RepoProvider.DataColumns.NAME);
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.PUBLIC_KEY)) {
|
||||
pubkey = values.getAsString(RepoProvider.DataColumns.PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if (values.containsKey(RepoProvider.DataColumns.PRIORITY)) {
|
||||
priority = toInt(values.getAsInteger(RepoProvider.DataColumns.PRIORITY));
|
||||
}
|
||||
}
|
||||
}
|
294
src/org/fdroid/fdroid/data/RepoProvider.java
Normal file
294
src/org/fdroid/fdroid/data/RepoProvider.java
Normal file
@ -0,0 +1,294 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import android.content.*;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import org.fdroid.fdroid.DB;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RepoProvider extends FDroidProvider {
|
||||
|
||||
public static final class Helper {
|
||||
|
||||
private Helper() {}
|
||||
|
||||
public static Repo findById(ContentResolver resolver, long repoId) {
|
||||
return findById(resolver, repoId, DataColumns.ALL);
|
||||
}
|
||||
|
||||
public static Repo findById(ContentResolver resolver, long repoId,
|
||||
String[] projection) {
|
||||
Uri uri = RepoProvider.getContentUri(repoId);
|
||||
Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
Repo repo = null;
|
||||
if (cursor != null) {
|
||||
cursor.moveToFirst();
|
||||
repo = new Repo(cursor);
|
||||
}
|
||||
return repo;
|
||||
}
|
||||
|
||||
public static Repo findByAddress(ContentResolver resolver,
|
||||
String address) {
|
||||
return findByAddress(resolver, address, DataColumns.ALL);
|
||||
}
|
||||
|
||||
public static Repo findByAddress(ContentResolver resolver,
|
||||
String address, String[] projection) {
|
||||
List<Repo> repos = findBy(
|
||||
resolver, DataColumns.ADDRESS, address, projection);
|
||||
return repos.size() > 0 ? repos.get(0) : null;
|
||||
}
|
||||
|
||||
public static List<Repo> all(ContentResolver resolver) {
|
||||
return all(resolver, DataColumns.ALL);
|
||||
}
|
||||
|
||||
public static List<Repo> all(ContentResolver resolver, String[] projection) {
|
||||
Uri uri = RepoProvider.getContentUri();
|
||||
Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
return cursorToList(cursor);
|
||||
}
|
||||
|
||||
private static List<Repo> findBy(ContentResolver resolver,
|
||||
String fieldName,
|
||||
String fieldValue,
|
||||
String[] projection) {
|
||||
Uri uri = RepoProvider.getContentUri();
|
||||
String[] args = { fieldValue };
|
||||
Cursor cursor = resolver.query(
|
||||
uri, projection, fieldName + " = ?", args, null );
|
||||
return cursorToList(cursor);
|
||||
}
|
||||
|
||||
private static List<Repo> cursorToList(Cursor cursor) {
|
||||
List<Repo> repos = new ArrayList<Repo>();
|
||||
if (cursor != null) {
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
repos.add(new Repo(cursor));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
return repos;
|
||||
}
|
||||
|
||||
public static void update(ContentResolver resolver, Repo repo,
|
||||
ContentValues values) {
|
||||
|
||||
// Change the name to the new address. Next time we update the repo
|
||||
// index file, it will populate the name field with the proper
|
||||
// name, but the best we can do is guess right now.
|
||||
if (values.containsKey(DataColumns.ADDRESS) &&
|
||||
!values.containsKey(DataColumns.NAME)) {
|
||||
String name = Repo.addressToName(values.getAsString(DataColumns.ADDRESS));
|
||||
values.put(DataColumns.NAME, name);
|
||||
}
|
||||
|
||||
// Recalculate the fingerprint if we are saving a public key (it is
|
||||
// probably a new public key. If not, we will get the same
|
||||
// fingerprint anyhow).
|
||||
if (values.containsKey(DataColumns.PUBLIC_KEY) &&
|
||||
values.containsKey(DataColumns.FINGERPRINT)) {
|
||||
|
||||
String publicKey = values.getAsString(DataColumns.PUBLIC_KEY);
|
||||
String fingerprint = values.getAsString(DataColumns.FINGERPRINT);
|
||||
if (publicKey != null && fingerprint == null) {
|
||||
values.put(DataColumns.FINGERPRINT, DB.calcFingerprint(publicKey));
|
||||
}
|
||||
}
|
||||
|
||||
if (values.containsKey(DataColumns.IN_USE)) {
|
||||
Integer inUse = values.getAsInteger(DataColumns.IN_USE);
|
||||
if (inUse != null && inUse == 0) {
|
||||
values.put(DataColumns.LAST_ETAG, (String)null);
|
||||
}
|
||||
}
|
||||
|
||||
Uri uri = getContentUri(repo.getId());
|
||||
String[] args = { Long.toString(repo.getId()) };
|
||||
resolver.update(uri, values, DataColumns._ID + " = ?", args );
|
||||
repo.setValues(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* This doesn't do anything other than call "insert" on the content
|
||||
* resolver, but I thought I'd put it here in the interests of having
|
||||
* each of the CRUD methods available in the helper class.
|
||||
*/
|
||||
public static void insert(ContentResolver resolver,
|
||||
ContentValues values) {
|
||||
Uri uri = RepoProvider.getContentUri();
|
||||
resolver.insert(uri, values);
|
||||
}
|
||||
|
||||
public static void remove(ContentResolver resolver, long repoId) {
|
||||
Uri uri = RepoProvider.getContentUri(repoId);
|
||||
resolver.delete(uri, null, null);
|
||||
}
|
||||
|
||||
public static void purgeApps(Repo repo, FDroidApp app) {
|
||||
// TODO: Once we have content providers for apps and apks, use them
|
||||
// to do this...
|
||||
DB db = DB.getDB();
|
||||
try {
|
||||
db.purgeApps(repo, app);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface DataColumns extends BaseColumns {
|
||||
public static String ADDRESS = "address";
|
||||
public static String NAME = "name";
|
||||
public static String DESCRIPTION = "description";
|
||||
public static String IN_USE = "inuse";
|
||||
public static String PRIORITY = "priority";
|
||||
public static String PUBLIC_KEY = "pubkey";
|
||||
public static String FINGERPRINT = "fingerprint";
|
||||
public static String MAX_AGE = "maxage";
|
||||
public static String LAST_ETAG = "lastetag";
|
||||
public static String LAST_UPDATED = "lastUpdated";
|
||||
public static String VERSION = "version";
|
||||
|
||||
public static String[] ALL = {
|
||||
_ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, PUBLIC_KEY,
|
||||
FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION
|
||||
};
|
||||
}
|
||||
|
||||
private static final String PROVIDER_NAME = "RepoProvider";
|
||||
|
||||
private static final UriMatcher matcher = new UriMatcher(-1);
|
||||
|
||||
static {
|
||||
matcher.addURI(AUTHORITY, PROVIDER_NAME, CODE_LIST);
|
||||
matcher.addURI(AUTHORITY, PROVIDER_NAME + "/#", CODE_SINGLE);
|
||||
}
|
||||
|
||||
public static Uri getContentUri() {
|
||||
return Uri.parse("content://" + AUTHORITY + "/" + PROVIDER_NAME);
|
||||
}
|
||||
|
||||
public static Uri getContentUri(long repoId) {
|
||||
return ContentUris.withAppendedId(getContentUri(), repoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableName() {
|
||||
return DBHelper.TABLE_REPO;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProviderName() {
|
||||
return "RepoProvider";
|
||||
}
|
||||
|
||||
protected UriMatcher getMatcher() {
|
||||
return matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
|
||||
switch (matcher.match(uri)) {
|
||||
case CODE_LIST:
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = "_ID ASC";
|
||||
}
|
||||
break;
|
||||
|
||||
case CODE_SINGLE:
|
||||
selection = ( selection == null ? "" : selection ) +
|
||||
"_ID = " + uri.getLastPathSegment();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e("FDroid", "Invalid URI for repo content provider: " + uri);
|
||||
throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri);
|
||||
}
|
||||
|
||||
Cursor cursor = read().query(getTableName(), projection, selection,
|
||||
selectionArgs, null, null, sortOrder);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
|
||||
if (!values.containsKey(DataColumns.ADDRESS)) {
|
||||
throw new UnsupportedOperationException("Cannot add repo without an address.");
|
||||
}
|
||||
|
||||
// The following fields have NOT NULL constraints in the DB, so need
|
||||
// to be present.
|
||||
|
||||
if (!values.containsKey(DataColumns.IN_USE)) {
|
||||
values.put(DataColumns.IN_USE, 1);
|
||||
}
|
||||
|
||||
if (!values.containsKey(DataColumns.PRIORITY)) {
|
||||
values.put(DataColumns.PRIORITY, 10);
|
||||
}
|
||||
|
||||
if (!values.containsKey(DataColumns.MAX_AGE)) {
|
||||
values.put(DataColumns.MAX_AGE, 0);
|
||||
}
|
||||
|
||||
if (!values.containsKey(DataColumns.VERSION)) {
|
||||
values.put(DataColumns.VERSION, 0);
|
||||
}
|
||||
|
||||
if (!values.containsKey(DataColumns.NAME)) {
|
||||
String address = values.getAsString(DataColumns.ADDRESS);
|
||||
values.put(DataColumns.NAME, Repo.addressToName(address));
|
||||
}
|
||||
|
||||
long id = write().insertOrThrow(getTableName(), null, values);
|
||||
Log.i("FDroid", "Inserted repo. Notifying provider change: '" + uri + "'.");
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return getContentUri(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String where, String[] whereArgs) {
|
||||
|
||||
switch (matcher.match(uri)) {
|
||||
case CODE_LIST:
|
||||
// Don't support deleting of multiple repos.
|
||||
return 0;
|
||||
|
||||
case CODE_SINGLE:
|
||||
where = ( where == null ? "" : where ) +
|
||||
"_ID = " + uri.getLastPathSegment();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e("FDroid", "Invalid URI for repo content provider: " + uri);
|
||||
throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri);
|
||||
}
|
||||
|
||||
int rowsAffected = write().delete(getTableName(), where, whereArgs);
|
||||
Log.i("FDroid", "Deleted repos. Notifying provider change: '" + uri + "'.");
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
||||
int numRows = write().update(getTableName(), values, where, whereArgs);
|
||||
Log.i("FDroid", "Updated repo. Notifying provider change: '" + uri + "'.");
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return numRows;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package org.fdroid.fdroid.updater;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@ -7,6 +8,8 @@ import org.fdroid.fdroid.DB;
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.RepoXMLHandler;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
@ -18,6 +21,7 @@ import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
abstract public class RepoUpdater {
|
||||
@ -26,7 +30,7 @@ abstract public class RepoUpdater {
|
||||
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) {
|
||||
public static RepoUpdater createUpdaterFor(Context ctx, Repo repo) {
|
||||
if (repo.pubkey != null) {
|
||||
return new SignedRepoUpdater(ctx, repo);
|
||||
} else {
|
||||
@ -35,12 +39,12 @@ abstract public class RepoUpdater {
|
||||
}
|
||||
|
||||
protected final Context context;
|
||||
protected final DB.Repo repo;
|
||||
protected final 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) {
|
||||
public RepoUpdater(Context ctx, Repo repo) {
|
||||
this.context = ctx;
|
||||
this.repo = repo;
|
||||
}
|
||||
@ -164,8 +168,6 @@ abstract public class RepoUpdater {
|
||||
if (hasChanged) {
|
||||
|
||||
downloadedFile = downloader.getFile();
|
||||
repo.lastetag = downloader.getETag();
|
||||
|
||||
indexFile = getIndexFromFile(downloadedFile);
|
||||
|
||||
// Process the index...
|
||||
@ -184,7 +186,7 @@ abstract public class RepoUpdater {
|
||||
new BufferedReader(new FileReader(indexFile)));
|
||||
|
||||
reader.parse(is);
|
||||
updateRepo(handler);
|
||||
updateRepo(handler, downloader.getETag());
|
||||
}
|
||||
} catch (SAXException e) {
|
||||
throw new UpdateException(
|
||||
@ -210,9 +212,15 @@ abstract public class RepoUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRepo(RepoXMLHandler handler) {
|
||||
private void updateRepo(RepoXMLHandler handler, String etag) {
|
||||
|
||||
boolean repoChanged = false;
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(RepoProvider.DataColumns.LAST_UPDATED, DB.DATE_FORMAT.format(new Date()));
|
||||
|
||||
if (repo.lastetag == null || !repo.lastetag.equals(etag)) {
|
||||
values.put(RepoProvider.DataColumns.LAST_ETAG, etag);
|
||||
}
|
||||
|
||||
// We read an unsigned index, but that indicates that
|
||||
// a signed version is now available...
|
||||
@ -224,54 +232,42 @@ abstract public class RepoUpdater {
|
||||
// information as the unsigned one does not...
|
||||
Log.d("FDroid",
|
||||
"Public key found - switching to signed repo for future updates");
|
||||
repo.pubkey = handler.getPubKey();
|
||||
repoChanged = true;
|
||||
values.put(RepoProvider.DataColumns.PUBLIC_KEY, handler.getPubKey());
|
||||
}
|
||||
|
||||
if (handler.getVersion() != -1 && handler.getVersion() != repo.version) {
|
||||
Log.d("FDroid", "Repo specified a new version: from "
|
||||
+ repo.version + " to " + handler.getVersion());
|
||||
repo.version = handler.getVersion();
|
||||
repoChanged = true;
|
||||
values.put(RepoProvider.DataColumns.VERSION, handler.getVersion());
|
||||
}
|
||||
|
||||
if (handler.getMaxAge() != -1 && handler.getMaxAge() != repo.maxage) {
|
||||
Log.d("FDroid",
|
||||
"Repo specified a new maximum age - updated");
|
||||
repo.maxage = handler.getMaxAge();
|
||||
repoChanged = true;
|
||||
values.put(RepoProvider.DataColumns.MAX_AGE, handler.getMaxAge());
|
||||
}
|
||||
|
||||
if (handler.getDescription() != null && !handler.getDescription().equals(repo.description)) {
|
||||
repo.description = handler.getDescription();
|
||||
repoChanged = true;
|
||||
values.put(RepoProvider.DataColumns.DESCRIPTION, handler.getDescription());
|
||||
}
|
||||
|
||||
if (handler.getName() != null && !handler.getName().equals(repo.name)) {
|
||||
repo.name = handler.getName();
|
||||
repoChanged = true;
|
||||
values.put(RepoProvider.DataColumns.NAME, handler.getName());
|
||||
}
|
||||
|
||||
if (repoChanged) {
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
db.updateRepoByAddress(repo);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
}
|
||||
RepoProvider.Helper.update(context.getContentResolver(), repo, values);
|
||||
}
|
||||
|
||||
public static class UpdateException extends Exception {
|
||||
|
||||
public final DB.Repo repo;
|
||||
public final Repo repo;
|
||||
|
||||
public UpdateException(DB.Repo repo, String message) {
|
||||
public UpdateException(Repo repo, String message) {
|
||||
super(message);
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
public UpdateException(DB.Repo repo, String message, Exception cause) {
|
||||
public UpdateException(Repo repo, String message, Exception cause) {
|
||||
super(message, cause);
|
||||
this.repo = repo;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import org.fdroid.fdroid.DB;
|
||||
import org.fdroid.fdroid.Hasher;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
|
||||
import java.io.*;
|
||||
@ -16,7 +17,7 @@ import java.util.jar.JarFile;
|
||||
|
||||
public class SignedRepoUpdater extends RepoUpdater {
|
||||
|
||||
public SignedRepoUpdater(Context ctx, DB.Repo repo) {
|
||||
public SignedRepoUpdater(Context ctx, Repo repo) {
|
||||
super(ctx, repo);
|
||||
}
|
||||
|
||||
|
@ -3,13 +3,14 @@ package org.fdroid.fdroid.updater;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import org.fdroid.fdroid.DB;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class UnsignedRepoUpdater extends RepoUpdater {
|
||||
|
||||
public UnsignedRepoUpdater(Context ctx, DB.Repo repo) {
|
||||
public UnsignedRepoUpdater(Context ctx, Repo repo) {
|
||||
super(ctx, repo);
|
||||
}
|
||||
|
||||
|
@ -1,34 +1,48 @@
|
||||
package org.fdroid.fdroid.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.*;
|
||||
|
||||
import org.fdroid.fdroid.DB;
|
||||
import org.fdroid.fdroid.ManageRepo;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.compat.SwitchCompat;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
|
||||
import java.util.List;
|
||||
public class RepoAdapter extends CursorAdapter {
|
||||
|
||||
public class RepoAdapter extends BaseAdapter {
|
||||
|
||||
private List<DB.Repo> repositories;
|
||||
private final ManageRepo activity;
|
||||
|
||||
public RepoAdapter(ManageRepo activity) {
|
||||
this.activity = activity;
|
||||
refresh();
|
||||
public interface EnabledListener {
|
||||
public void onSetEnabled(Repo repo, boolean isEnabled);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
repositories = db.getRepos();
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
private static final int SWITCH_ID = 10000;
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
|
||||
private EnabledListener enabledListener;
|
||||
|
||||
public RepoAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
inflater = LayoutInflater.from(context);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
|
||||
public RepoAdapter(Context context, Cursor c, boolean autoRequery) {
|
||||
super(context, c, autoRequery);
|
||||
inflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
public RepoAdapter(Context context, Cursor c) {
|
||||
super(context, c);
|
||||
inflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
public void setEnabledListener(EnabledListener listener) {
|
||||
enabledListener = listener;
|
||||
}
|
||||
|
||||
public boolean hasStableIds() {
|
||||
@ -36,54 +50,45 @@ public class RepoAdapter extends BaseAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return repositories.size();
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = inflater.inflate(R.layout.repo_item, null);
|
||||
CompoundButton switchView = addSwitchToView(view, context);
|
||||
setupView(cursor, view, switchView);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return repositories.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).hashCode();
|
||||
}
|
||||
|
||||
private static final int SWITCH_ID = 10000;
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
|
||||
final DB.Repo repository = repositories.get(position);
|
||||
|
||||
CompoundButton switchView;
|
||||
if (view == null) {
|
||||
view = activity.getLayoutInflater().inflate(R.layout.repo_item,null);
|
||||
switchView = addSwitchToView(view);
|
||||
} else {
|
||||
switchView = (CompoundButton)view.findViewById(SWITCH_ID);
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
CompoundButton switchView = (CompoundButton)view.findViewById(SWITCH_ID);
|
||||
|
||||
// Remove old listener (because we are reusing this view, we don't want
|
||||
// to invoke the listener for the last repo to use it - particularly
|
||||
// because we are potentially about to change the checked status
|
||||
// which would in turn invoke this listener....
|
||||
switchView.setOnCheckedChangeListener(null);
|
||||
setupView(cursor, view, switchView);
|
||||
}
|
||||
|
||||
switchView.setChecked(repository.inuse);
|
||||
|
||||
private void setupView(Cursor cursor, View view, CompoundButton switchView) {
|
||||
|
||||
final Repo repo = new Repo(cursor);
|
||||
|
||||
switchView.setChecked(repo.inuse);
|
||||
|
||||
// Add this listener *after* setting the checked status, so we don't
|
||||
// invoke the listener while setting up the view...
|
||||
switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
activity.setRepoEnabled(repository, isChecked);
|
||||
if (enabledListener != null) {
|
||||
enabledListener.onSetEnabled(repo, isChecked);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TextView nameView = (TextView)view.findViewById(R.id.repo_name);
|
||||
nameView.setText(repository.getName());
|
||||
nameView.setText(repo.getName());
|
||||
RelativeLayout.LayoutParams nameViewLayout =
|
||||
(RelativeLayout.LayoutParams)nameView.getLayoutParams();
|
||||
nameViewLayout.addRule(RelativeLayout.LEFT_OF, switchView.getId());
|
||||
@ -91,17 +96,15 @@ public class RepoAdapter extends BaseAdapter {
|
||||
// If we set the signed view to GONE instead of INVISIBLE, then the
|
||||
// height of each list item varies.
|
||||
View signedView = view.findViewById(R.id.repo_unsigned);
|
||||
if (repository.isSigned()) {
|
||||
if (repo.isSigned()) {
|
||||
signedView.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
signedView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private CompoundButton addSwitchToView(View parent) {
|
||||
SwitchCompat switchBuilder = SwitchCompat.create(activity);
|
||||
private CompoundButton addSwitchToView(View parent, Context context) {
|
||||
SwitchCompat switchBuilder = SwitchCompat.create(context);
|
||||
CompoundButton switchView = switchBuilder.createSwitch();
|
||||
switchView.setId(SWITCH_ID);
|
||||
RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(
|
||||
|
@ -1,32 +1,23 @@
|
||||
package org.fdroid.fdroid.views;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
import org.fdroid.fdroid.DB;
|
||||
import org.fdroid.fdroid.DB.Repo;
|
||||
import org.fdroid.fdroid.compat.ActionBarCompat;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.views.fragments.RepoDetailsFragment;
|
||||
|
||||
public class RepoDetailsActivity extends FragmentActivity implements RepoDetailsFragment.OnRepoChangeListener {
|
||||
|
||||
public static final String ACTION_IS_DELETED = "isDeleted";
|
||||
public static final String ACTION_IS_ENABLED = "isEnabled";
|
||||
public static final String ACTION_IS_DISABLED = "isDisabled";
|
||||
public static final String ACTION_IS_CHANGED = "isChanged";
|
||||
|
||||
public static final String DATA_REPO_ID = "repoId";
|
||||
|
||||
private int repoId;
|
||||
public class RepoDetailsActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
long repoId = getIntent().getLongExtra(RepoDetailsFragment.ARG_REPO_ID, 0);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
RepoDetailsFragment fragment = new RepoDetailsFragment();
|
||||
fragment.setRepoChangeListener(this);
|
||||
RepoDetailsFragment fragment = new RepoDetailsFragment(repoId);
|
||||
fragment.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
@ -34,48 +25,11 @@ public class RepoDetailsActivity extends FragmentActivity implements RepoDetails
|
||||
.commit();
|
||||
}
|
||||
|
||||
repoId = getIntent().getIntExtra(RepoDetailsFragment.ARG_REPO_ID, -1);
|
||||
String[] projection = new String[] { RepoProvider.DataColumns.NAME };
|
||||
Repo repo = RepoProvider.Helper.findById(getContentResolver(), repoId, projection);
|
||||
|
||||
DB db = DB.getDB();
|
||||
Repo repo = db.getRepo(repoId);
|
||||
DB.releaseDB();
|
||||
|
||||
ActionBarCompat abCompat = ActionBarCompat.create(this);
|
||||
abCompat.setDisplayHomeAsUpEnabled(true);
|
||||
ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
|
||||
setTitle(repo.getName());
|
||||
}
|
||||
|
||||
private void finishWithAction(String actionName) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(actionName, true);
|
||||
data.putExtra(DATA_REPO_ID, repoId);
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteRepo(DB.Repo repo) {
|
||||
finishWithAction(ACTION_IS_DELETED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepoDetailsChanged(DB.Repo repo) {
|
||||
// Do nothing...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRepo(DB.Repo repo) {
|
||||
finishWithAction(ACTION_IS_ENABLED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisableRepo(DB.Repo repo) {
|
||||
finishWithAction(ACTION_IS_DISABLED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdatePerformed(DB.Repo repo) {
|
||||
// do nothing - the actual update is done by the repo fragment...
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,8 +2,12 @@ package org.fdroid.fdroid.views.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentValues;
|
||||
import android.content.DialogInterface;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.text.Editable;
|
||||
@ -12,8 +16,8 @@ import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import org.fdroid.fdroid.*;
|
||||
|
||||
import java.util.List;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
|
||||
public class RepoDetailsFragment extends Fragment {
|
||||
|
||||
@ -49,34 +53,15 @@ public class RepoDetailsFragment extends Fragment {
|
||||
private static final int DELETE = 0;
|
||||
private static final int UPDATE = 1;
|
||||
|
||||
public void setRepoChangeListener(OnRepoChangeListener listener) {
|
||||
repoChangeListener = listener;
|
||||
}
|
||||
|
||||
private OnRepoChangeListener repoChangeListener;
|
||||
|
||||
public static interface OnRepoChangeListener {
|
||||
|
||||
/**
|
||||
* This fragment is responsible for getting confirmation from the
|
||||
* user, so you should presume that the user has already consented
|
||||
* and confirmed to the deletion.
|
||||
*/
|
||||
public void onDeleteRepo(DB.Repo repo);
|
||||
|
||||
public void onRepoDetailsChanged(DB.Repo repo);
|
||||
|
||||
public void onEnableRepo(DB.Repo repo);
|
||||
|
||||
public void onDisableRepo(DB.Repo repo);
|
||||
|
||||
public void onUpdatePerformed(DB.Repo repo);
|
||||
private final long repoId;
|
||||
|
||||
public RepoDetailsFragment(long repoId) {
|
||||
this.repoId = repoId;
|
||||
}
|
||||
|
||||
// TODO: Currently initialised in onCreateView. Not sure if that is the
|
||||
// best way to go about this...
|
||||
private DB.Repo repo;
|
||||
private Repo repo;
|
||||
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
@ -88,20 +73,13 @@ public class RepoDetailsFragment extends Fragment {
|
||||
* have been updated. The safest way to deal with this is to reload the
|
||||
* repo object directly from the database.
|
||||
*/
|
||||
private void reloadRepoDetails() {
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
repo = db.getRepo(repo.id);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
private Repo loadRepoDetails() {
|
||||
return RepoProvider.Helper.findById(getActivity().getContentResolver(), repoId);
|
||||
}
|
||||
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
int repoId = getArguments().getInt(ARG_REPO_ID);
|
||||
DB db = DB.getDB();
|
||||
repo = db.getRepo(repoId);
|
||||
DB.releaseDB();
|
||||
|
||||
repo = loadRepoDetails();
|
||||
|
||||
if (repo == null) {
|
||||
Log.e("FDroid", "Error showing details for repo '" + repoId + "'");
|
||||
@ -186,7 +164,7 @@ public class RepoDetailsFragment extends Fragment {
|
||||
lastUpdated.setText(lastUpdate);
|
||||
}
|
||||
|
||||
private void setupDescription(ViewGroup parent, DB.Repo repo) {
|
||||
private void setupDescription(ViewGroup parent, Repo repo) {
|
||||
|
||||
TextView descriptionLabel = (TextView)parent.findViewById(R.id.label_description);
|
||||
TextView description = (TextView)parent.findViewById(R.id.text_description);
|
||||
@ -210,20 +188,20 @@ public class RepoDetailsFragment extends Fragment {
|
||||
* list can be updated. We will perform the update ourselves though.
|
||||
*/
|
||||
private void performUpdate() {
|
||||
repo.enable((FDroidApp)getActivity().getApplication());
|
||||
// Ensure repo is enabled before updating...
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.IN_USE, 1);
|
||||
RepoProvider.Helper.update(getActivity().getContentResolver(), repo, values);
|
||||
|
||||
UpdateService.updateRepoNow(repo.address, getActivity()).setListener(new ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
if (event.type == UpdateService.STATUS_COMPLETE_AND_SAME ||
|
||||
event.type == UpdateService.STATUS_COMPLETE_WITH_CHANGES) {
|
||||
reloadRepoDetails();
|
||||
if (event.type == UpdateService.STATUS_COMPLETE_WITH_CHANGES) {
|
||||
repo = loadRepoDetails();
|
||||
updateView((ViewGroup)getView());
|
||||
}
|
||||
}
|
||||
});
|
||||
if (repoChangeListener != null) {
|
||||
repoChangeListener.onUpdatePerformed(repo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,18 +216,15 @@ public class RepoDetailsFragment extends Fragment {
|
||||
public void afterTextChanged(Editable s) {}
|
||||
|
||||
@Override
|
||||
// TODO: This is called each character change, resulting in a DB query.
|
||||
// Doesn't exactly cause performance problems,
|
||||
// but seems silly not to go for more of a "focus out" event then
|
||||
// this "text changed" event.
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (!repo.address.equals(s.toString())) {
|
||||
repo.address = s.toString();
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
db.updateRepo(repo);
|
||||
} finally {
|
||||
DB.releaseDB();
|
||||
}
|
||||
if (repoChangeListener != null) {
|
||||
repoChangeListener.onRepoDetailsChanged(repo);
|
||||
}
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, s.toString());
|
||||
RepoProvider.Helper.update(getActivity().getContentResolver(), repo, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -295,10 +270,9 @@ public class RepoDetailsFragment extends Fragment {
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (repoChangeListener != null) {
|
||||
DB.Repo repo = RepoDetailsFragment.this.repo;
|
||||
repoChangeListener.onDeleteRepo(repo);
|
||||
}
|
||||
Repo repo = RepoDetailsFragment.this.repo;
|
||||
RepoProvider.Helper.remove(getActivity().getContentResolver(), repo.getId());
|
||||
getActivity().finish();
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@ -310,7 +284,7 @@ public class RepoDetailsFragment extends Fragment {
|
||||
).show();
|
||||
}
|
||||
|
||||
private void setupRepoFingerprint(ViewGroup parent, DB.Repo repo) {
|
||||
private void setupRepoFingerprint(ViewGroup parent, Repo repo) {
|
||||
TextView repoFingerprintView = (TextView)parent.findViewById(R.id.text_repo_fingerprint);
|
||||
TextView repoFingerprintDescView = (TextView)parent.findViewById(R.id.text_repo_fingerprint_description);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user