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:
Peter Serwylo 2014-01-23 12:27:38 +11:00
parent cad37b0d55
commit f8893431fb
18 changed files with 1023 additions and 798 deletions

View File

@ -33,6 +33,12 @@
android:allowBackup="true" android:allowBackup="true"
android:theme="@style/AppThemeDark" android:theme="@style/AppThemeDark"
android:supportsRtl="false" > android:supportsRtl="false" >
<provider
android:authorities="org.fdroid.fdroid.data"
android:name="org.fdroid.fdroid.data.RepoProvider"
android:exported="false"/>
<activity <activity
android:name=".FDroid" android:name=".FDroid"
android:configChanges="keyboardHidden|orientation|screenSize" > android:configChanges="keyboardHidden|orientation|screenSize" >

View File

@ -25,6 +25,8 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.xml.sax.XMLReader; import org.xml.sax.XMLReader;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -859,20 +861,13 @@ public class AppDetails extends ListActivity {
// Install the version of this app denoted by 'app.curApk'. // Install the version of this app denoted by 'app.curApk'.
private void install() { private void install() {
String ra = null; String [] projection = { RepoProvider.DataColumns.ADDRESS };
try { Repo repo = RepoProvider.Helper.findById(
DB db = DB.getDB(); getContentResolver(), app.curApk.repo, projection);
DB.Repo repo = db.getRepo(app.curApk.repo); if (repo == null || repo.address == null) {
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)
return; return;
final String repoaddress = ra; }
final String repoaddress = repo.address;
if (!app.curApk.compatible) { if (!app.curApk.compatible) {
AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this); AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);

View File

@ -55,6 +55,7 @@ import org.fdroid.fdroid.compat.Compatibility;
import org.fdroid.fdroid.compat.ContextCompat; import org.fdroid.fdroid.compat.ContextCompat;
import org.fdroid.fdroid.compat.SupportedArchitectures; import org.fdroid.fdroid.compat.SupportedArchitectures;
import org.fdroid.fdroid.data.DBHelper; import org.fdroid.fdroid.data.DBHelper;
import org.fdroid.fdroid.data.Repo;
public class DB { public class DB {
@ -290,7 +291,7 @@ public class DB {
public String version; public String version;
public int vercode; public int vercode;
public int detail_size; // Size in bytes - 0 means we don't know! 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_hash;
public String detail_hashType; public String detail_hashType;
public int minSdkVersion; // 0 if unknown 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 int countAppsForRepo(long id) {
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) {
String[] selection = { "COUNT(distinct id)" }; String[] selection = { "COUNT(distinct id)" };
String[] selectionArgs = { Integer.toString(id) }; String[] selectionArgs = { Long.toString(id) };
Cursor result = db.query( Cursor result = db.query(
TABLE_APK, selection, "repo = ?", selectionArgs, "repo", null, null); TABLE_APK, selection, "repo = ?", selectionArgs, "repo", null, null);
if (result.getCount() > 0) { if (result.getCount() > 0) {
@ -554,8 +453,7 @@ public class DB {
// The date format used for storing dates (e.g. lastupdated, added) in the // The date format used for storing dates (e.g. lastupdated, added) in the
// database. // database.
public static SimpleDateFormat dateFormat = new SimpleDateFormat( public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
"yyyy-MM-dd", Locale.ENGLISH);
private DB(Context ctx) { 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 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) { if (repo == 0 || repo == apk.repo) {
Cursor cursor = null; Cursor cursor = null;
try { try {
@ -692,7 +590,7 @@ public class DB {
// Populate the details for the given app, if necessary. // Populate the details for the given app, if necessary.
// If 'apkrepo' is non-zero, only apks from that repo are // If 'apkrepo' is non-zero, only apks from that repo are
// populated (this is used during the update process) // 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) { if (!app.detail_Populated) {
populateAppDetails(app); populateAppDetails(app);
} }
@ -747,10 +645,10 @@ public class DB {
app.curVercode = c.getInt(9); app.curVercode = c.getInt(9);
String sAdded = c.getString(10); String sAdded = c.getString(10);
app.added = (sAdded == null || sAdded.length() == 0) ? null app.added = (sAdded == null || sAdded.length() == 0) ? null
: dateFormat.parse(sAdded); : DATE_FORMAT.parse(sAdded);
String sLastUpdated = c.getString(11); String sLastUpdated = c.getString(11);
app.lastUpdated = (sLastUpdated == null || sLastUpdated app.lastUpdated = (sLastUpdated == null || sLastUpdated
.length() == 0) ? null : dateFormat .length() == 0) ? null : DATE_FORMAT
.parse(sLastUpdated); .parse(sLastUpdated);
app.compatible = c.getInt(12) == 1; app.compatible = c.getInt(12) == 1;
app.ignoreAllUpdates = c.getInt(13) == 1; app.ignoreAllUpdates = c.getInt(13) == 1;
@ -783,12 +681,16 @@ public class DB {
Log.d("FDroid", "Read app data from database " + " (took " Log.d("FDroid", "Read app data from database " + " (took "
+ (System.currentTimeMillis() - startTime) + " ms)"); + (System.currentTimeMillis() - startTime) + " ms)");
List<Repo> repos = getRepos(); String query = "SELECT apk.id, apk.version, apk.vercode, apk.sig,"
cols = new String[] { "id", "version", "vercode", "sig", "srcname", + " apk.srcname, apk.apkName, apk.minSdkVersion, "
"apkName", "minSdkVersion", "added", "features", "nativecode", + " apk.added, apk.features, apk.nativecode, "
"compatible", "repo" }; + " apk.compatible, apk.repo, repo.version, repo.address "
c = db.query(TABLE_APK, cols, null, null, null, null, + " FROM " + TABLE_APK + " as apk "
"vercode desc"); + " LEFT JOIN " + DBHelper.TABLE_REPO + " as repo "
+ " ON repo._id = apk.repo "
+ " ORDER BY apk.vercode DESC";
c = db.rawQuery(query, null);
c.moveToFirst(); c.moveToFirst();
DisplayMetrics metrics = mContext.getResources() DisplayMetrics metrics = mContext.getResources()
@ -829,21 +731,19 @@ public class DB {
apk.minSdkVersion = c.getInt(6); apk.minSdkVersion = c.getInt(6);
String sApkAdded = c.getString(7); String sApkAdded = c.getString(7);
apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null
: dateFormat.parse(sApkAdded); : DATE_FORMAT.parse(sApkAdded);
apk.features = CommaSeparatedList.make(c.getString(8)); apk.features = CommaSeparatedList.make(c.getString(8));
apk.nativecode = CommaSeparatedList.make(c.getString(9)); apk.nativecode = CommaSeparatedList.make(c.getString(9));
apk.compatible = compatible; apk.compatible = compatible;
apk.repo = repoid; apk.repo = repoid;
app.apks.add(apk); app.apks.add(apk);
if (app.iconUrl == null && app.icon != null) { if (app.iconUrl == null && app.icon != null) {
for (DB.Repo repo : repos) { int repoVersion = c.getInt(12);
if (repo.id != repoid) continue; String repoAddress = c.getString(13);
if (repo.version >= 11) { if (repoVersion >= 11) {
app.iconUrl = repo.address + iconsDir + app.icon; app.iconUrl = repoAddress + iconsDir + app.icon;
} else { } else {
app.iconUrl = repo.address + "/icons/" + app.icon; app.iconUrl = repoAddress + "/icons/" + app.icon;
}
break;
} }
} }
c.moveToNext(); c.moveToNext();
@ -1153,10 +1053,10 @@ public class DB {
values.put("dogecoinAddr", upapp.detail_dogecoinAddr); values.put("dogecoinAddr", upapp.detail_dogecoinAddr);
values.put("flattrID", upapp.detail_flattrID); values.put("flattrID", upapp.detail_flattrID);
values.put("added", values.put("added",
upapp.added == null ? "" : dateFormat.format(upapp.added)); upapp.added == null ? "" : DATE_FORMAT.format(upapp.added));
values.put( values.put(
"lastUpdated", "lastUpdated",
upapp.added == null ? "" : dateFormat upapp.added == null ? "" : DATE_FORMAT
.format(upapp.lastUpdated)); .format(upapp.lastUpdated));
values.put("curVersion", upapp.curVersion); values.put("curVersion", upapp.curVersion);
values.put("curVercode", upapp.curVercode); values.put("curVercode", upapp.curVercode);
@ -1201,7 +1101,7 @@ public class DB {
values.put("apkName", upapk.apkName); values.put("apkName", upapk.apkName);
values.put("minSdkVersion", upapk.minSdkVersion); values.put("minSdkVersion", upapk.minSdkVersion);
values.put("added", values.put("added",
upapk.added == null ? "" : dateFormat.format(upapk.added)); upapk.added == null ? "" : DATE_FORMAT.format(upapk.added));
values.put("permissions", values.put("permissions",
CommaSeparatedList.str(upapk.detail_permissions)); CommaSeparatedList.str(upapk.detail_permissions));
values.put("features", CommaSeparatedList.str(upapk.features)); 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) { public void setIgnoreUpdates(String appid, boolean All, int This) {
db.execSQL("update " + TABLE_APP + " set" db.execSQL("update " + TABLE_APP + " set"
+ " ignoreAllUpdates=" + (All ? '1' : '0') + " ignoreAllUpdates=" + (All ? '1' : '0')
@ -1322,120 +1123,11 @@ public class DB {
+ " where id = ?", new String[] { appid }); + " where id = ?", new String[] { appid });
} }
public void updateRepoByAddress(Repo repo) { public void purgeApps(Repo repo, FDroidApp fdroid) {
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;
db.beginTransaction(); 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 { try {
for (Repo repo : repos) { db.delete(TABLE_APK, "repo = ?", new String[] { Long.toString(repo.getId()) });
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 });
}
}
List<App> apps = getApps(false); List<App> apps = getApps(false);
for (App app : apps) { for (App app : apps) {
if (app.apks.isEmpty()) { if (app.apks.isEmpty()) {
@ -1446,6 +1138,8 @@ public class DB {
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
fdroid.invalidateAllApps();
} }
public int getSynchronizationMode() { public int getSynchronizationMode() {

View File

@ -21,34 +21,39 @@ package org.fdroid.fdroid;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ListActivity; import android.content.ContentValues;
import android.content.DialogInterface; 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.content.Intent;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.*; import android.widget.*;
import org.fdroid.fdroid.DB.Repo;
import org.fdroid.fdroid.compat.ActionBarCompat; import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.compat.ClipboardCompat; 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.RepoAdapter;
import org.fdroid.fdroid.views.RepoDetailsActivity; import org.fdroid.fdroid.views.RepoDetailsActivity;
import org.fdroid.fdroid.views.fragments.RepoDetailsFragment; import org.fdroid.fdroid.views.fragments.RepoDetailsFragment;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.Locale;
public class ManageRepo extends ListActivity { public class ManageRepo extends FragmentActivity {
private static final String DEFAULT_NEW_REPO_TEXT = "https://";
private final int ADD_REPO = 1;
private final int UPDATE_REPOS = 2;
/** /**
* If we have a new repo added, or the address of a repo has changed, then * 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"; 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 { private enum PositiveAction {
ADD_NEW, ENABLE, IGNORE ADD_NEW, ENABLE, IGNORE
} }
@ -74,16 +195,14 @@ public class ManageRepo extends ListActivity {
private boolean isImportingRepo = false; private boolean isImportingRepo = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
((FDroidApp) getApplication()).applyTheme(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ActionBarCompat abCompat = ActionBarCompat.create(this); setHasOptionsMenu(true);
abCompat.setDisplayHomeAsUpEnabled(true);
repoAdapter = new RepoAdapter(this); repoAdapter = new RepoAdapter(getActivity(), null);
repoAdapter.setEnabledListener(this);
setListAdapter(repoAdapter); setListAdapter(repoAdapter);
/* /*
@ -106,7 +225,7 @@ public class ManageRepo extends ListActivity {
*/ */
/* let's see if someone is trying to send us a new repo */ /* 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 */ /* an URL from a click or a QRCode scan */
Uri uri = intent.getData(); Uri uri = intent.getData();
if (uri != null) { if (uri != null) {
@ -139,27 +258,25 @@ public class ManageRepo extends ListActivity {
} }
@Override @Override
protected void onResume() { public void onResume() {
super.onResume(); super.onResume();
refreshList();
//Starts a new or restarts an existing Loader in this manager
getLoaderManager().restartLoader(0, null, this);
} }
@Override @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); super.onListItemClick(l, v, position, id);
DB.Repo repo = (DB.Repo)getListView().getItemAtPosition(position); Repo repo = new Repo((Cursor)getListView().getItemAtPosition(position));
editRepo(repo); editRepo(repo);
} }
private void refreshList() {
repoAdapter.refresh();
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu, inflater);
MenuItem updateItem = menu.add(Menu.NONE, UPDATE_REPOS, 1, MenuItem updateItem = menu.add(Menu.NONE, UPDATE_REPOS, 1,
R.string.menu_update_repo).setIcon(R.drawable.ic_menu_refresh); R.string.menu_update_repo).setIcon(R.drawable.ic_menu_refresh);
@ -171,102 +288,23 @@ public class ManageRepo extends ListActivity {
android.R.drawable.ic_menu_add); android.R.drawable.ic_menu_add);
MenuItemCompat.setShowAsAction(addItem, MenuItemCompat.setShowAsAction(addItem,
MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
return true;
} }
public static final int SHOW_REPO_DETAILS = 1; public static final int SHOW_REPO_DETAILS = 1;
public void editRepo(DB.Repo repo) { public void editRepo(Repo repo) {
Log.d("FDroid", "Showing details screen for repo: '" + repo + "'."); Intent intent = new Intent(getActivity(), RepoDetailsActivity.class);
Intent intent = new Intent(this, RepoDetailsActivity.class); intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.getId());
intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.id);
startActivityForResult(intent, SHOW_REPO_DETAILS); 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() { private void updateRepos() {
UpdateService.updateNow(this).setListener(new ProgressListener() { UpdateService.updateNow(getActivity()).setListener(new ProgressListener() {
@Override @Override
public void onProgress(Event event) { public void onProgress(Event event) {
// No need to prompt to update any more, we just did it! // No need to prompt to update any more, we just did it!
changed = false; changed = false;
refreshList();
} }
}); });
} }
@ -276,14 +314,14 @@ public class ManageRepo extends ListActivity {
} }
private void showAddRepo(String newAddress, String newFingerprint) { private void showAddRepo(String newAddress, String newFingerprint) {
View view = getLayoutInflater(null).inflate(R.layout.addrepo, null);
View view = getLayoutInflater().inflate(R.layout.addrepo, null); final AlertDialog alrt = new AlertDialog.Builder(getActivity()).setView(view).create();
final AlertDialog alrt = new AlertDialog.Builder(this).setView(view).create();
final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri); final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
final EditText fingerprintEditText = (EditText) view.findViewById(R.id.edit_fingerprint); final EditText fingerprintEditText = (EditText) view.findViewById(R.id.edit_fingerprint);
List<Repo> repos = getRepos(); final Repo repo = ( newAddress != null && isImportingRepo )
final Repo repo = newAddress != null && isImportingRepo ? getRepoByAddress(newAddress, repos) : null; ? RepoProvider.Helper.findByAddress(getActivity().getContentResolver(), newAddress)
: null;
alrt.setIcon(android.R.drawable.ic_menu_add); alrt.setIcon(android.R.drawable.ic_menu_add);
alrt.setTitle(getString(R.string.repo_add_title)); alrt.setTitle(getString(R.string.repo_add_title));
@ -312,8 +350,8 @@ public class ManageRepo extends ListActivity {
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
setResult(Activity.RESULT_CANCELED); getActivity().setResult(Activity.RESULT_CANCELED);
finish(); getActivity().finish();
return; return;
} }
}); });
@ -338,7 +376,7 @@ public class ManageRepo extends ListActivity {
// this entry already exists and is not enabled, offer to enable it // this entry already exists and is not enabled, offer to enable it
if (repo.inuse) { if (repo.inuse) {
alrt.dismiss(); 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; return;
} else { } else {
overwriteMessage.setText(R.string.repo_exists_enable); overwriteMessage.setText(R.string.repo_exists_enable);
@ -373,12 +411,10 @@ public class ManageRepo extends ListActivity {
* Adds a new repo to the database. * Adds a new repo to the database.
*/ */
private void createNewRepo(String address, String fingerprint) { private void createNewRepo(String address, String fingerprint) {
try { ContentValues values = new ContentValues(2);
DB db = DB.getDB(); values.put(RepoProvider.DataColumns.ADDRESS, address);
db.addRepo(address, null, null, 0, 10, null, fingerprint, 0, true); values.put(RepoProvider.DataColumns.FINGERPRINT, fingerprint);
} finally { RepoProvider.Helper.insert(getActivity().getContentResolver(), values);
DB.releaseDB();
}
finishedAddingRepo(); finishedAddingRepo();
} }
@ -386,13 +422,10 @@ public class ManageRepo extends ListActivity {
* Seeing as this repo already exists, we will force it to be enabled again. * Seeing as this repo already exists, we will force it to be enabled again.
*/ */
private void createNewRepo(Repo repo) { 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; repo.inuse = true;
try {
DB db = DB.getDB();
db.updateRepoByAddress(repo);
} finally {
DB.releaseDB();
}
finishedAddingRepo(); finishedAddingRepo();
} }
@ -404,17 +437,13 @@ public class ManageRepo extends ListActivity {
private void finishedAddingRepo() { private void finishedAddingRepo() {
changed = true; changed = true;
if (isImportingRepo) { if (isImportingRepo) {
setResult(Activity.RESULT_OK); getActivity().setResult(Activity.RESULT_OK);
finish(); getActivity().finish();
} else {
refreshList();
} }
} }
@Override @Override
public boolean onMenuItemSelected(int featureId, MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
super.onMenuItemSelected(featureId, item);
if (item.getItemId() == ADD_REPO) { if (item.getItemId() == ADD_REPO) {
showAddRepo(); showAddRepo();
@ -424,7 +453,7 @@ public class ManageRepo extends ListActivity {
return true; return true;
} }
return false; return super.onOptionsItemSelected(item);
} }
/** /**
@ -432,7 +461,7 @@ public class ManageRepo extends ListActivity {
* Otherwise return "https://". * Otherwise return "https://".
*/ */
private String getNewRepoUri() { private String getNewRepoUri() {
ClipboardCompat clipboard = ClipboardCompat.create(this); ClipboardCompat clipboard = ClipboardCompat.create(getActivity());
String text = clipboard.getText(); String text = clipboard.getText();
if (text != null) { if (text != null) {
try { try {
@ -447,46 +476,4 @@ public class ManageRepo extends ListActivity {
} }
return text; 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();
}
}
} }

View File

@ -20,6 +20,7 @@
package org.fdroid.fdroid; package org.fdroid.fdroid;
import android.os.Bundle; import android.os.Bundle;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.updater.RepoUpdater; import org.fdroid.fdroid.updater.RepoUpdater;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -33,7 +34,7 @@ import java.util.Map;
public class RepoXMLHandler extends DefaultHandler { public class RepoXMLHandler extends DefaultHandler {
// The repo we're processing. // The repo we're processing.
private DB.Repo repo; private Repo repo;
private Map<String, DB.App> apps; private Map<String, DB.App> apps;
private List<DB.App> appsList; private List<DB.App> appsList;
@ -62,7 +63,7 @@ public class RepoXMLHandler extends DefaultHandler {
private int totalAppCount; 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.repo = repo;
this.apps = new HashMap<String, DB.App>(); this.apps = new HashMap<String, DB.App>();
for (DB.App app : appsList) this.apps.put(app.id, 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")) { } else if (curel.equals("added")) {
try { try {
curapk.added = str.length() == 0 ? null : DB.dateFormat curapk.added = str.length() == 0 ? null : DB.DATE_FORMAT
.parse(str); .parse(str);
} catch (ParseException e) { } catch (ParseException e) {
curapk.added = null; curapk.added = null;
@ -204,7 +205,7 @@ public class RepoXMLHandler extends DefaultHandler {
curapp.detail_trackerURL = str; curapp.detail_trackerURL = str;
} else if (curel.equals("added")) { } else if (curel.equals("added")) {
try { try {
curapp.added = str.length() == 0 ? null : DB.dateFormat curapp.added = str.length() == 0 ? null : DB.DATE_FORMAT
.parse(str); .parse(str);
} catch (ParseException e) { } catch (ParseException e) {
curapp.added = null; curapp.added = null;
@ -212,7 +213,7 @@ public class RepoXMLHandler extends DefaultHandler {
} else if (curel.equals("lastupdated")) { } else if (curel.equals("lastupdated")) {
try { try {
curapp.lastUpdated = str.length() == 0 ? null curapp.lastUpdated = str.length() == 0 ? null
: DB.dateFormat.parse(str); : DB.DATE_FORMAT.parse(str);
} catch (ParseException e) { } catch (ParseException e) {
curapp.lastUpdated = null; curapp.lastUpdated = null;
} }
@ -281,7 +282,7 @@ public class RepoXMLHandler extends DefaultHandler {
} else if (localName.equals("package") && curapp != null && curapk == null) { } else if (localName.equals("package") && curapp != null && curapk == null) {
curapk = new DB.Apk(); curapk = new DB.Apk();
curapk.id = curapp.id; curapk.id = curapp.id;
curapk.repo = repo.id; curapk.repo = repo.getId();
hashType = null; hashType = null;
} else if (localName.equals("hash") && curapk != null) { } else if (localName.equals("hash") && curapk != null) {

View File

@ -41,12 +41,16 @@ import android.os.Parcelable;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder; import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.updater.RepoUpdater; import org.fdroid.fdroid.updater.RepoUpdater;
public class UpdateService extends IntentService implements ProgressListener { 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 // Grab some preliminary information, then we can release the
// database while we do all the downloading, etc... // database while we do all the downloading, etc...
int updates = 0; int updates = 0;
List<DB.Repo> repos; List<Repo> repos;
List<DB.App> apps; List<DB.App> apps;
try { try {
DB db = DB.getDB(); DB db = DB.getDB();
repos = db.getRepos();
apps = db.getApps(false); apps = db.getApps(false);
} finally { } finally {
DB.releaseDB(); DB.releaseDB();
} }
repos = RepoProvider.Helper.all(getContentResolver());
// Process each repo... // Process each repo...
List<DB.App> updatingApps = new ArrayList<DB.App>(); List<DB.App> updatingApps = new ArrayList<DB.App>();
Set<Integer> keeprepos = new TreeSet<Integer>(); Set<Long> keeprepos = new TreeSet<Long>();
boolean changes = false; boolean changes = false;
boolean update; boolean update;
for (DB.Repo repo : repos) { for (Repo repo : repos) {
if (!repo.inuse) if (!repo.inuse)
continue; continue;
// are we updating all repos, or just one? // are we updating all repos, or just one?
@ -306,7 +311,7 @@ public class UpdateService extends IntentService implements ProgressListener {
if (address.equals(repo.address)) { if (address.equals(repo.address)) {
update = true; update = true;
} else { } else {
keeprepos.add(repo.id); keeprepos.add(repo.getId());
update = false; update = false;
} }
} }
@ -321,7 +326,7 @@ public class UpdateService extends IntentService implements ProgressListener {
updatingApps.addAll(updater.getApps()); updatingApps.addAll(updater.getApps());
changes = true; changes = true;
} else { } else {
keeprepos.add(repo.id); keeprepos.add(repo.getId());
} }
} catch (RepoUpdater.UpdateException e) { } catch (RepoUpdater.UpdateException e) {
errmsg += (errmsg.length() == 0 ? "" : "\n") + e.getMessage(); 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 // Need to flag things we're keeping despite having received
// no data about during the update. (i.e. stuff from a repo // no data about during the update. (i.e. stuff from a repo
// that we know is unchanged due to the etag) // that we know is unchanged due to the etag)
for (int keep : keeprepos) { for (long keep : keeprepos) {
for (DB.App app : apps) { for (DB.App app : apps) {
boolean keepapp = false; boolean keepapp = false;
for (DB.Apk apk : app.apks) { for (DB.Apk apk : app.apks) {
@ -378,8 +383,6 @@ public class UpdateService extends IntentService implements ProgressListener {
db.updateApplication(app); db.updateApplication(app);
} }
db.endUpdate(); db.endUpdate();
for (DB.Repo repo : repos)
db.writeLastEtag(repo);
} catch (Exception ex) { } catch (Exception ex) {
db.cancelUpdate(); db.cancelUpdate();
Log.e("FDroid", "Exception during update processing:\n" Log.e("FDroid", "Exception during update processing:\n"

View File

@ -36,6 +36,7 @@ import java.util.Locale;
import android.content.Context; import android.content.Context;
import com.nostra13.universalimageloader.utils.StorageUtils; import com.nostra13.universalimageloader.utils.StorageUtils;
import org.fdroid.fdroid.data.Repo;
public final class Utils { public final class Utils {
@ -159,7 +160,7 @@ public final class Utils {
return count; return count;
} }
public static String formatFingerprint(DB.Repo repo) { public static String formatFingerprint(Repo repo) {
return formatFingerprint(repo.pubkey); return formatFingerprint(repo.pubkey);
} }

View File

@ -1,26 +1,27 @@
package org.fdroid.fdroid.compat; package org.fdroid.fdroid.compat;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.Switch; import android.widget.Switch;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import org.fdroid.fdroid.ManageRepo;
public abstract class SwitchCompat extends Compatibility { public abstract class SwitchCompat extends Compatibility {
protected final ManageRepo activity; protected final Context context;
protected SwitchCompat(ManageRepo activity) { protected SwitchCompat(Context context) {
this.activity = activity; this.context = context;
} }
public abstract CompoundButton createSwitch(); public abstract CompoundButton createSwitch();
public static SwitchCompat create(ManageRepo activity) { public static SwitchCompat create(Context context) {
if (hasApi(14)) { if (hasApi(14)) {
return new IceCreamSwitch(activity); return new IceCreamSwitch(context);
} else { } else {
return new OldSwitch(activity); return new OldSwitch(context);
} }
} }
@ -29,24 +30,24 @@ public abstract class SwitchCompat extends Compatibility {
@TargetApi(14) @TargetApi(14)
class IceCreamSwitch extends SwitchCompat { class IceCreamSwitch extends SwitchCompat {
protected IceCreamSwitch(ManageRepo activity) { protected IceCreamSwitch(Context context) {
super(activity); super(context);
} }
@Override @Override
public CompoundButton createSwitch() { public CompoundButton createSwitch() {
return new Switch(activity); return new Switch(context);
} }
} }
class OldSwitch extends SwitchCompat { class OldSwitch extends SwitchCompat {
protected OldSwitch(ManageRepo activity) { protected OldSwitch(Context context) {
super(activity); super(context);
} }
@Override @Override
public CompoundButton createSwitch() { public CompoundButton createSwitch() {
return new ToggleButton(activity); return new ToggleButton(context);
} }
} }

View File

@ -16,11 +16,15 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "fdroid"; public static final String DATABASE_NAME = "fdroid";
public static final String TABLE_REPO = "fdroid_repo";
private static final String CREATE_TABLE_REPO = "create table " 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, " + "name text, description text, inuse integer not null, "
+ "priority integer not null, pubkey text, fingerprint text, " + "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);"; + "lastetag text, lastUpdated string);";
private static final String CREATE_TABLE_APK = "create table " + DB.TABLE_APK 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," + "ignoreThisUpdate int not null,"
+ "primary key(id));"; + "primary key(id));";
private static final int DB_VERSION = 35; private static final int DB_VERSION = 37;
private Context context; private Context context;
@ -58,6 +62,79 @@ public class DBHelper extends SQLiteOpenHelper {
this.context = context; 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 @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
@ -80,7 +157,7 @@ public class DBHelper extends SQLiteOpenHelper {
values.put("inuse", 1); values.put("inuse", 1);
values.put("priority", 10); values.put("priority", 10);
values.put("lastetag", (String) null); values.put("lastetag", (String) null);
db.insert(DB.TABLE_REPO, null, values); db.insert(TABLE_REPO, null, values);
values = new ContentValues(); values = new ContentValues();
values.put("address", values.put("address",
@ -97,7 +174,7 @@ public class DBHelper extends SQLiteOpenHelper {
values.put("inuse", 0); values.put("inuse", 0);
values.put("priority", 20); values.put("priority", 20);
values.put("lastetag", (String) null); values.put("lastetag", (String) null);
db.insert(DB.TABLE_REPO, null, values); db.insert(TABLE_REPO, null, values);
} }
@Override @Override
@ -118,6 +195,8 @@ public class DBHelper extends SQLiteOpenHelper {
addMaxAgeToRepo(db, oldVersion); addMaxAgeToRepo(db, oldVersion);
addVersionToRepo(db, oldVersion); addVersionToRepo(db, oldVersion);
addLastUpdatedToRepo(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) { private void migradeRepoTable(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 20) { if (oldVersion < 20) {
List<DB.Repo> oldrepos = new ArrayList<DB.Repo>(); List<Repo> oldrepos = new ArrayList<Repo>();
Cursor c = db.query(DB.TABLE_REPO, Cursor c = db.query(TABLE_REPO,
new String[] { "address", "inuse", "pubkey" }, new String[] { "address", "inuse", "pubkey" },
null, null, null, null, null); null, null, null, null, null);
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
DB.Repo repo = new DB.Repo(); Repo repo = new Repo();
repo.address = c.getString(0); repo.address = c.getString(0);
repo.inuse = (c.getInt(1) == 1); repo.inuse = (c.getInt(1) == 1);
repo.pubkey = c.getString(2); repo.pubkey = c.getString(2);
@ -140,16 +219,16 @@ public class DBHelper extends SQLiteOpenHelper {
c.moveToNext(); c.moveToNext();
} }
c.close(); c.close();
db.execSQL("drop table " + DB.TABLE_REPO); db.execSQL("drop table " + TABLE_REPO);
db.execSQL(CREATE_TABLE_REPO); db.execSQL(CREATE_TABLE_REPO);
for (DB.Repo repo : oldrepos) { for (Repo repo : oldrepos) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("address", repo.address); values.put("address", repo.address);
values.put("inuse", repo.inuse); values.put("inuse", repo.inuse);
values.put("priority", 10); values.put("priority", 10);
values.put("pubkey", repo.pubkey); values.put("pubkey", repo.pubkey);
values.put("lastetag", (String) null); 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) { private void addNameAndDescriptionToRepo(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 21) { if (oldVersion < 21) {
if (!columnExists(db, DB.TABLE_REPO, "name")) if (!columnExists(db, TABLE_REPO, "name"))
db.execSQL("alter table " + DB.TABLE_REPO + " add column name text"); db.execSQL("alter table " + TABLE_REPO + " add column name text");
if (!columnExists(db, DB.TABLE_REPO, "description")) if (!columnExists(db, TABLE_REPO, "description"))
db.execSQL("alter table " + DB.TABLE_REPO + " add column description text"); db.execSQL("alter table " + TABLE_REPO + " add column description text");
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("name", context.getString(R.string.default_repo_name)); values.put("name", context.getString(R.string.default_repo_name));
values.put("description", context.getString(R.string.default_repo_description)); 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)}); context.getString(R.string.default_repo_address)});
values.clear(); values.clear();
values.put("name", context.getString(R.string.default_repo_name2)); values.put("name", context.getString(R.string.default_repo_name2));
values.put("description", context.getString(R.string.default_repo_description2)); 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) }); context.getString(R.string.default_repo_address2) });
} }
@ -184,44 +263,44 @@ public class DBHelper extends SQLiteOpenHelper {
*/ */
private void addFingerprintToRepo(SQLiteDatabase db, int oldVersion) { private void addFingerprintToRepo(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 29) { if (oldVersion < 29) {
if (!columnExists(db, DB.TABLE_REPO, "fingerprint")) if (!columnExists(db, TABLE_REPO, "fingerprint"))
db.execSQL("alter table " + DB.TABLE_REPO + " add column fingerprint text"); db.execSQL("alter table " + TABLE_REPO + " add column fingerprint text");
List<DB.Repo> oldrepos = new ArrayList<DB.Repo>(); List<Repo> oldrepos = new ArrayList<Repo>();
Cursor c = db.query(DB.TABLE_REPO, Cursor c = db.query(TABLE_REPO,
new String[] { "address", "pubkey" }, new String[] { "address", "pubkey" },
null, null, null, null, null); null, null, null, null, null);
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
DB.Repo repo = new DB.Repo(); Repo repo = new Repo();
repo.address = c.getString(0); repo.address = c.getString(0);
repo.pubkey = c.getString(1); repo.pubkey = c.getString(1);
oldrepos.add(repo); oldrepos.add(repo);
c.moveToNext(); c.moveToNext();
} }
c.close(); c.close();
for (DB.Repo repo : oldrepos) { for (Repo repo : oldrepos) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("fingerprint", DB.calcFingerprint(repo.pubkey)); 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) { private void addMaxAgeToRepo(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 30) { 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) { private void addVersionToRepo(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 33 && !columnExists(db, DB.TABLE_REPO, "version")) { if (oldVersion < 33 && !columnExists(db, TABLE_REPO, "version")) {
db.execSQL("alter table " + DB.TABLE_REPO + " add column version integer not null default 0"); db.execSQL("alter table " + TABLE_REPO + " add column version integer not null default 0");
} }
} }
private void addLastUpdatedToRepo(SQLiteDatabase db, int oldVersion) { private void addLastUpdatedToRepo(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 35 && !columnExists(db, DB.TABLE_REPO, "lastUpdated")) { if (oldVersion < 35 && !columnExists(db, TABLE_REPO, "lastUpdated")) {
db.execSQL("Alter table " + DB.TABLE_REPO + " add column lastUpdated string"); db.execSQL("Alter table " + TABLE_REPO + " add column lastUpdated string");
} }
} }
@ -230,7 +309,7 @@ public class DBHelper extends SQLiteOpenHelper {
.putBoolean("triedEmptyUpdate", false).commit(); .putBoolean("triedEmptyUpdate", false).commit();
db.execSQL("drop table " + DB.TABLE_APP); db.execSQL("drop table " + DB.TABLE_APP);
db.execSQL("drop table " + DB.TABLE_APK); 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); createAppApk(db);
} }

View 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();
}

View 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));
}
}
}

View 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;
}
}

View File

@ -1,5 +1,6 @@
package org.fdroid.fdroid.updater; package org.fdroid.fdroid.updater;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@ -7,6 +8,8 @@ import org.fdroid.fdroid.DB;
import org.fdroid.fdroid.ProgressListener; import org.fdroid.fdroid.ProgressListener;
import org.fdroid.fdroid.RepoXMLHandler; import org.fdroid.fdroid.RepoXMLHandler;
import org.fdroid.fdroid.Utils; 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.fdroid.fdroid.net.Downloader;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -18,6 +21,7 @@ import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import java.io.*; import java.io.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
abstract public class RepoUpdater { abstract public class RepoUpdater {
@ -26,7 +30,7 @@ abstract public class RepoUpdater {
public static final int PROGRESS_TYPE_PROCESS_XML = 2; public static final int PROGRESS_TYPE_PROCESS_XML = 2;
public static final String PROGRESS_DATA_REPO = "repo"; 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) { if (repo.pubkey != null) {
return new SignedRepoUpdater(ctx, repo); return new SignedRepoUpdater(ctx, repo);
} else { } else {
@ -35,12 +39,12 @@ abstract public class RepoUpdater {
} }
protected final Context context; protected final Context context;
protected final DB.Repo repo; protected final Repo repo;
protected final List<DB.App> apps = new ArrayList<DB.App>(); protected final List<DB.App> apps = new ArrayList<DB.App>();
protected boolean hasChanged = false; protected boolean hasChanged = false;
protected ProgressListener progressListener; protected ProgressListener progressListener;
public RepoUpdater(Context ctx, DB.Repo repo) { public RepoUpdater(Context ctx, Repo repo) {
this.context = ctx; this.context = ctx;
this.repo = repo; this.repo = repo;
} }
@ -164,8 +168,6 @@ abstract public class RepoUpdater {
if (hasChanged) { if (hasChanged) {
downloadedFile = downloader.getFile(); downloadedFile = downloader.getFile();
repo.lastetag = downloader.getETag();
indexFile = getIndexFromFile(downloadedFile); indexFile = getIndexFromFile(downloadedFile);
// Process the index... // Process the index...
@ -184,7 +186,7 @@ abstract public class RepoUpdater {
new BufferedReader(new FileReader(indexFile))); new BufferedReader(new FileReader(indexFile)));
reader.parse(is); reader.parse(is);
updateRepo(handler); updateRepo(handler, downloader.getETag());
} }
} catch (SAXException e) { } catch (SAXException e) {
throw new UpdateException( 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 // We read an unsigned index, but that indicates that
// a signed version is now available... // a signed version is now available...
@ -224,54 +232,42 @@ abstract public class RepoUpdater {
// information as the unsigned one does not... // information as the unsigned one does not...
Log.d("FDroid", Log.d("FDroid",
"Public key found - switching to signed repo for future updates"); "Public key found - switching to signed repo for future updates");
repo.pubkey = handler.getPubKey(); values.put(RepoProvider.DataColumns.PUBLIC_KEY, handler.getPubKey());
repoChanged = true;
} }
if (handler.getVersion() != -1 && handler.getVersion() != repo.version) { if (handler.getVersion() != -1 && handler.getVersion() != repo.version) {
Log.d("FDroid", "Repo specified a new version: from " Log.d("FDroid", "Repo specified a new version: from "
+ repo.version + " to " + handler.getVersion()); + repo.version + " to " + handler.getVersion());
repo.version = handler.getVersion(); values.put(RepoProvider.DataColumns.VERSION, handler.getVersion());
repoChanged = true;
} }
if (handler.getMaxAge() != -1 && handler.getMaxAge() != repo.maxage) { if (handler.getMaxAge() != -1 && handler.getMaxAge() != repo.maxage) {
Log.d("FDroid", Log.d("FDroid",
"Repo specified a new maximum age - updated"); "Repo specified a new maximum age - updated");
repo.maxage = handler.getMaxAge(); values.put(RepoProvider.DataColumns.MAX_AGE, handler.getMaxAge());
repoChanged = true;
} }
if (handler.getDescription() != null && !handler.getDescription().equals(repo.description)) { if (handler.getDescription() != null && !handler.getDescription().equals(repo.description)) {
repo.description = handler.getDescription(); values.put(RepoProvider.DataColumns.DESCRIPTION, handler.getDescription());
repoChanged = true;
} }
if (handler.getName() != null && !handler.getName().equals(repo.name)) { if (handler.getName() != null && !handler.getName().equals(repo.name)) {
repo.name = handler.getName(); values.put(RepoProvider.DataColumns.NAME, handler.getName());
repoChanged = true;
} }
if (repoChanged) { RepoProvider.Helper.update(context.getContentResolver(), repo, values);
try {
DB db = DB.getDB();
db.updateRepoByAddress(repo);
} finally {
DB.releaseDB();
}
}
} }
public static class UpdateException extends Exception { 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); super(message);
this.repo = repo; this.repo = repo;
} }
public UpdateException(DB.Repo repo, String message, Exception cause) { public UpdateException(Repo repo, String message, Exception cause) {
super(message, cause); super(message, cause);
this.repo = repo; this.repo = repo;
} }

View File

@ -6,6 +6,7 @@ import org.fdroid.fdroid.DB;
import org.fdroid.fdroid.Hasher; import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.Downloader;
import java.io.*; import java.io.*;
@ -16,7 +17,7 @@ import java.util.jar.JarFile;
public class SignedRepoUpdater extends RepoUpdater { public class SignedRepoUpdater extends RepoUpdater {
public SignedRepoUpdater(Context ctx, DB.Repo repo) { public SignedRepoUpdater(Context ctx, Repo repo) {
super(ctx, repo); super(ctx, repo);
} }

View File

@ -3,13 +3,14 @@ package org.fdroid.fdroid.updater;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import org.fdroid.fdroid.DB; import org.fdroid.fdroid.DB;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.Downloader;
import java.io.File; import java.io.File;
public class UnsignedRepoUpdater extends RepoUpdater { public class UnsignedRepoUpdater extends RepoUpdater {
public UnsignedRepoUpdater(Context ctx, DB.Repo repo) { public UnsignedRepoUpdater(Context ctx, Repo repo) {
super(ctx, repo); super(ctx, repo);
} }

View File

@ -1,34 +1,48 @@
package org.fdroid.fdroid.views; 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.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.*;
import org.fdroid.fdroid.DB; import android.widget.CompoundButton;
import org.fdroid.fdroid.ManageRepo; import android.widget.RelativeLayout;
import android.widget.TextView;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.compat.SwitchCompat; 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 { public interface EnabledListener {
public void onSetEnabled(Repo repo, boolean isEnabled);
private List<DB.Repo> repositories;
private final ManageRepo activity;
public RepoAdapter(ManageRepo activity) {
this.activity = activity;
refresh();
} }
public void refresh() { private static final int SWITCH_ID = 10000;
try {
DB db = DB.getDB(); private final LayoutInflater inflater;
repositories = db.getRepos();
} finally { private EnabledListener enabledListener;
DB.releaseDB();
} public RepoAdapter(Context context, Cursor c, int flags) {
notifyDataSetChanged(); super(context, c, flags);
inflater = LayoutInflater.from(context);
}
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() { public boolean hasStableIds() {
@ -36,54 +50,45 @@ public class RepoAdapter extends BaseAdapter {
} }
@Override @Override
public int getCount() { public View newView(Context context, Cursor cursor, ViewGroup parent) {
return repositories.size(); View view = inflater.inflate(R.layout.repo_item, null);
CompoundButton switchView = addSwitchToView(view, context);
setupView(cursor, view, switchView);
return view;
} }
@Override @Override
public Object getItem(int position) { public void bindView(View view, Context context, Cursor cursor) {
return repositories.get(position); 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);
} }
@Override
public long getItemId(int position) {
return getItem(position).hashCode();
}
private static final int SWITCH_ID = 10000; private void setupView(Cursor cursor, View view, CompoundButton switchView) {
@Override final Repo repo = new Repo(cursor);
public View getView(int position, View view, ViewGroup parent) {
final DB.Repo repository = repositories.get(position); switchView.setChecked(repo.inuse);
CompoundButton switchView;
if (view == null) {
view = activity.getLayoutInflater().inflate(R.layout.repo_item,null);
switchView = addSwitchToView(view);
} else {
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);
}
switchView.setChecked(repository.inuse);
// Add this listener *after* setting the checked status, so we don't // Add this listener *after* setting the checked status, so we don't
// invoke the listener while setting up the view... // invoke the listener while setting up the view...
switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 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); TextView nameView = (TextView)view.findViewById(R.id.repo_name);
nameView.setText(repository.getName()); nameView.setText(repo.getName());
RelativeLayout.LayoutParams nameViewLayout = RelativeLayout.LayoutParams nameViewLayout =
(RelativeLayout.LayoutParams)nameView.getLayoutParams(); (RelativeLayout.LayoutParams)nameView.getLayoutParams();
nameViewLayout.addRule(RelativeLayout.LEFT_OF, switchView.getId()); 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 // If we set the signed view to GONE instead of INVISIBLE, then the
// height of each list item varies. // height of each list item varies.
View signedView = view.findViewById(R.id.repo_unsigned); View signedView = view.findViewById(R.id.repo_unsigned);
if (repository.isSigned()) { if (repo.isSigned()) {
signedView.setVisibility(View.INVISIBLE); signedView.setVisibility(View.INVISIBLE);
} else { } else {
signedView.setVisibility(View.VISIBLE); signedView.setVisibility(View.VISIBLE);
} }
return view;
} }
private CompoundButton addSwitchToView(View parent) { private CompoundButton addSwitchToView(View parent, Context context) {
SwitchCompat switchBuilder = SwitchCompat.create(activity); SwitchCompat switchBuilder = SwitchCompat.create(context);
CompoundButton switchView = switchBuilder.createSwitch(); CompoundButton switchView = switchBuilder.createSwitch();
switchView.setId(SWITCH_ID); switchView.setId(SWITCH_ID);
RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(

View File

@ -1,32 +1,23 @@
package org.fdroid.fdroid.views; package org.fdroid.fdroid.views;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentActivity; 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.compat.ActionBarCompat;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.views.fragments.RepoDetailsFragment; import org.fdroid.fdroid.views.fragments.RepoDetailsFragment;
public class RepoDetailsActivity extends FragmentActivity implements RepoDetailsFragment.OnRepoChangeListener { public class RepoDetailsActivity extends FragmentActivity {
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;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
long repoId = getIntent().getLongExtra(RepoDetailsFragment.ARG_REPO_ID, 0);
if (savedInstanceState == null) { if (savedInstanceState == null) {
RepoDetailsFragment fragment = new RepoDetailsFragment(); RepoDetailsFragment fragment = new RepoDetailsFragment(repoId);
fragment.setRepoChangeListener(this);
fragment.setArguments(getIntent().getExtras()); fragment.setArguments(getIntent().getExtras());
getSupportFragmentManager() getSupportFragmentManager()
.beginTransaction() .beginTransaction()
@ -34,48 +25,11 @@ public class RepoDetailsActivity extends FragmentActivity implements RepoDetails
.commit(); .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(); ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
Repo repo = db.getRepo(repoId);
DB.releaseDB();
ActionBarCompat abCompat = ActionBarCompat.create(this);
abCompat.setDisplayHomeAsUpEnabled(true);
setTitle(repo.getName()); 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...
}
} }

View File

@ -2,8 +2,12 @@ package org.fdroid.fdroid.views.fragments;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.text.Editable; import android.text.Editable;
@ -12,8 +16,8 @@ import android.util.Log;
import android.view.*; import android.view.*;
import android.widget.*; import android.widget.*;
import org.fdroid.fdroid.*; import org.fdroid.fdroid.*;
import org.fdroid.fdroid.data.Repo;
import java.util.List; import org.fdroid.fdroid.data.RepoProvider;
public class RepoDetailsFragment extends Fragment { public class RepoDetailsFragment extends Fragment {
@ -49,34 +53,15 @@ public class RepoDetailsFragment extends Fragment {
private static final int DELETE = 0; private static final int DELETE = 0;
private static final int UPDATE = 1; private static final int UPDATE = 1;
public void setRepoChangeListener(OnRepoChangeListener listener) { private final long repoId;
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);
public RepoDetailsFragment(long repoId) {
this.repoId = repoId;
} }
// TODO: Currently initialised in onCreateView. Not sure if that is the // TODO: Currently initialised in onCreateView. Not sure if that is the
// best way to go about this... // best way to go about this...
private DB.Repo repo; private Repo repo;
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(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 * have been updated. The safest way to deal with this is to reload the
* repo object directly from the database. * repo object directly from the database.
*/ */
private void reloadRepoDetails() { private Repo loadRepoDetails() {
try { return RepoProvider.Helper.findById(getActivity().getContentResolver(), repoId);
DB db = DB.getDB();
repo = db.getRepo(repo.id);
} finally {
DB.releaseDB();
}
} }
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
int repoId = getArguments().getInt(ARG_REPO_ID);
DB db = DB.getDB(); repo = loadRepoDetails();
repo = db.getRepo(repoId);
DB.releaseDB();
if (repo == null) { if (repo == null) {
Log.e("FDroid", "Error showing details for repo '" + repoId + "'"); Log.e("FDroid", "Error showing details for repo '" + repoId + "'");
@ -186,7 +164,7 @@ public class RepoDetailsFragment extends Fragment {
lastUpdated.setText(lastUpdate); 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 descriptionLabel = (TextView)parent.findViewById(R.id.label_description);
TextView description = (TextView)parent.findViewById(R.id.text_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. * list can be updated. We will perform the update ourselves though.
*/ */
private void performUpdate() { 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() { UpdateService.updateRepoNow(repo.address, getActivity()).setListener(new ProgressListener() {
@Override @Override
public void onProgress(Event event) { public void onProgress(Event event) {
if (event.type == UpdateService.STATUS_COMPLETE_AND_SAME || if (event.type == UpdateService.STATUS_COMPLETE_WITH_CHANGES) {
event.type == UpdateService.STATUS_COMPLETE_WITH_CHANGES) { repo = loadRepoDetails();
reloadRepoDetails();
updateView((ViewGroup)getView()); updateView((ViewGroup)getView());
} }
} }
}); });
if (repoChangeListener != null) {
repoChangeListener.onUpdatePerformed(repo);
}
} }
/** /**
@ -238,18 +216,15 @@ public class RepoDetailsFragment extends Fragment {
public void afterTextChanged(Editable s) {} public void afterTextChanged(Editable s) {}
@Override @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) { public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!repo.address.equals(s.toString())) { if (!repo.address.equals(s.toString())) {
repo.address = s.toString(); ContentValues values = new ContentValues(1);
try { values.put(RepoProvider.DataColumns.ADDRESS, s.toString());
DB db = DB.getDB(); RepoProvider.Helper.update(getActivity().getContentResolver(), repo, values);
db.updateRepo(repo);
} finally {
DB.releaseDB();
}
if (repoChangeListener != null) {
repoChangeListener.onRepoDetailsChanged(repo);
}
} }
} }
} }
@ -293,24 +268,23 @@ public class RepoDetailsFragment extends Fragment {
.setIcon(android.R.drawable.ic_menu_delete) .setIcon(android.R.drawable.ic_menu_delete)
.setMessage(R.string.repo_confirm_delete_body) .setMessage(R.string.repo_confirm_delete_body)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { .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);
}
}
}).setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
// Do nothing... Repo repo = RepoDetailsFragment.this.repo;
RepoProvider.Helper.remove(getActivity().getContentResolver(), repo.getId());
getActivity().finish();
}
}).setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Do nothing...
}
} }
}
).show(); ).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 repoFingerprintView = (TextView)parent.findViewById(R.id.text_repo_fingerprint);
TextView repoFingerprintDescView = (TextView)parent.findViewById(R.id.text_repo_fingerprint_description); TextView repoFingerprintDescView = (TextView)parent.findViewById(R.id.text_repo_fingerprint_description);