Merge branch 'prep-for-new-index' into 'master'

prep for new index-v1

Closes #819

See merge request !439
This commit is contained in:
Peter Serwylo 2017-02-28 02:35:15 +00:00
commit 6ac62d791a
14 changed files with 257 additions and 174 deletions

View File

@ -10,10 +10,16 @@ before_script:
test:
script:
- cd app
- ./tools/langs-list-check.py
- ./tools/check-string-format.py
- cd ..
- ./gradlew assemble -PdisablePreDex
# always report on lint errors to the build log
- sed -i -e 's,textReport .*,textReport true,' app/build.gradle
- ./gradlew lint -PdisablePreDex
- ./gradlew pmd -PdisablePreDex
- ./gradlew checkstyle -PdisablePreDex
- ./gradlew test -PdisablePreDex || {
for log in app/build/reports/*ests/*/*ml; do
echo "read $log here:";
@ -64,24 +70,6 @@ connected24:
done
- exit $EXITVALUE
pmd:
script:
- ./gradlew pmd -PdisablePreDex
checkstyle:
script:
- ./gradlew checkstyle -PdisablePreDex
tools:
before_script:
- echo "ignored, no gradle needed"
script:
- cd app
- ./tools/langs-list-check.py
- ./tools/check-string-format.py
after_script:
- echo "ignored, no gradle needed"
after_script:
# this file changes every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock

View File

@ -1163,23 +1163,23 @@ public class AppDetails extends AppCompatActivity {
App app = appDetails.getApp();
switch (v.getId()) {
case R.id.website:
url = app.webURL;
url = app.webSite;
break;
case R.id.email:
final String subject = Uri.encode(getString(R.string.app_details_subject, app.name));
url = "mailto:" + app.email + "?subject=" + subject;
url = "mailto:" + app.authorEmail + "?subject=" + subject;
break;
case R.id.source:
url = app.sourceURL;
url = app.sourceCode;
break;
case R.id.issues:
url = app.trackerURL;
url = app.issueTracker;
break;
case R.id.changelog:
url = app.changelogURL;
url = app.changelog;
break;
case R.id.donate:
url = app.donateURL;
url = app.donate;
break;
case R.id.bitcoin:
url = app.getBitcoinUri();
@ -1267,7 +1267,7 @@ public class AppDetails extends AppCompatActivity {
// Website button
View tv = view.findViewById(R.id.website);
if (!TextUtils.isEmpty(app.webURL)) {
if (!TextUtils.isEmpty(app.webSite)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1275,7 +1275,7 @@ public class AppDetails extends AppCompatActivity {
// Email button
tv = view.findViewById(R.id.email);
if (!TextUtils.isEmpty(app.email)) {
if (!TextUtils.isEmpty(app.authorEmail)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1283,7 +1283,7 @@ public class AppDetails extends AppCompatActivity {
// Source button
tv = view.findViewById(R.id.source);
if (!TextUtils.isEmpty(app.sourceURL)) {
if (!TextUtils.isEmpty(app.sourceCode)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1291,7 +1291,7 @@ public class AppDetails extends AppCompatActivity {
// Issues button
tv = view.findViewById(R.id.issues);
if (!TextUtils.isEmpty(app.trackerURL)) {
if (!TextUtils.isEmpty(app.issueTracker)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1299,7 +1299,7 @@ public class AppDetails extends AppCompatActivity {
// Changelog button
tv = view.findViewById(R.id.changelog);
if (!TextUtils.isEmpty(app.changelogURL)) {
if (!TextUtils.isEmpty(app.changelog)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1307,7 +1307,7 @@ public class AppDetails extends AppCompatActivity {
// Donate button
tv = view.findViewById(R.id.donate);
if (!TextUtils.isEmpty(app.donateURL)) {
if (!TextUtils.isEmpty(app.donate)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1315,7 +1315,7 @@ public class AppDetails extends AppCompatActivity {
// Bitcoin
tv = view.findViewById(R.id.bitcoin);
if (!TextUtils.isEmpty(app.bitcoinAddr)) {
if (!TextUtils.isEmpty(app.bitcoin)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1323,7 +1323,7 @@ public class AppDetails extends AppCompatActivity {
// Litecoin
tv = view.findViewById(R.id.litecoin);
if (!TextUtils.isEmpty(app.litecoinAddr)) {
if (!TextUtils.isEmpty(app.litecoin)) {
tv.setOnClickListener(onClickListener);
} else {
tv.setVisibility(View.GONE);
@ -1642,8 +1642,8 @@ public class AppDetails extends AppCompatActivity {
btMain.setEnabled(true);
}
TextView author = (TextView) view.findViewById(R.id.author);
if (!TextUtils.isEmpty(app.author)) {
author.setText(getString(R.string.by_author) + " " + app.author);
if (!TextUtils.isEmpty(app.authorName)) {
author.setText(getString(R.string.by_author) + " " + app.authorName);
author.setVisibility(View.VISIBLE);
}
TextView currentVersion = (TextView) view.findViewById(R.id.current_version);

View File

@ -58,6 +58,7 @@ import java.security.CodeSigner;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.jar.JarEntry;
@ -195,9 +196,11 @@ public class RepoUpdater {
private RepoXMLHandler.IndexReceiver createIndexReceiver() {
return new RepoXMLHandler.IndexReceiver() {
@Override
public void receiveRepo(String name, String description, String signingCert, int maxAge, int version, long timestamp) {
public void receiveRepo(String name, String description, String signingCert, int maxAge,
int version, long timestamp, String icon, String[] mirrors) {
signingCertFromIndexXml = signingCert;
repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version, timestamp);
repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version,
timestamp, icon, mirrors);
}
@Override
@ -294,7 +297,9 @@ public class RepoUpdater {
* Update tracking data for the repo represented by this instance (index version, etag,
* description, human-readable name, etc.
*/
private ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge, int version, long timestamp) {
private ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge,
int version, long timestamp, String icon,
String[] mirrors) {
ContentValues values = new ContentValues();
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
@ -329,6 +334,14 @@ public class RepoUpdater {
// timestamp.
values.put(RepoTable.Cols.TIMESTAMP, timestamp);
if (icon != null && !icon.equals(repo.icon)) {
values.put(RepoTable.Cols.ICON, icon);
}
if (mirrors != null && mirrors.length > 0 && !Arrays.equals(mirrors, repo.mirrors)) {
values.put(RepoTable.Cols.MIRRORS, Utils.serializeCommaSeparatedString(mirrors));
}
return values;
}

View File

@ -59,6 +59,8 @@ public class RepoXMLHandler extends DefaultHandler {
private long repoTimestamp;
private String repoDescription;
private String repoName;
private String repoIcon;
private final ArrayList<String> repoMirrors = new ArrayList<>();
/**
* Set of requested permissions per package/APK
@ -73,7 +75,8 @@ public class RepoXMLHandler extends DefaultHandler {
private final StringBuilder curchars = new StringBuilder();
public interface IndexReceiver {
void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp);
void receiveRepo(String name, String description, String signingCert, int maxage, int version,
long timestamp, String icon, String[] mirrors);
void receiveApp(App app, List<Apk> packages);
@ -205,34 +208,34 @@ public class RepoXMLHandler extends DefaultHandler {
curapp.license = str;
break;
case "author":
curapp.author = str;
curapp.authorName = str;
break;
case "email":
curapp.email = str;
curapp.authorEmail = str;
break;
case "source":
curapp.sourceURL = str;
curapp.sourceCode = str;
break;
case "changelog":
curapp.changelogURL = str;
curapp.changelog = str;
break;
case "donate":
curapp.donateURL = str;
curapp.donate = str;
break;
case "bitcoin":
curapp.bitcoinAddr = str;
curapp.bitcoin = str;
break;
case "litecoin":
curapp.litecoinAddr = str;
curapp.litecoin = str;
break;
case "flattr":
curapp.flattrID = str;
break;
case "web":
curapp.webURL = str;
curapp.webSite = str;
break;
case "tracker":
curapp.trackerURL = str;
curapp.issueTracker = str;
break;
case "added":
curapp.added = Utils.parseDate(str, null);
@ -258,6 +261,8 @@ public class RepoXMLHandler extends DefaultHandler {
}
} else if ("description".equals(localName)) {
repoDescription = cleanWhiteSpace(str);
} else if ("mirror".equals(localName)) {
repoMirrors.add(str);
}
}
@ -312,7 +317,8 @@ public class RepoXMLHandler extends DefaultHandler {
}
private void onRepoParsed() {
receiver.receiveRepo(repoName, repoDescription, repoSigningCert, repoMaxAge, repoVersion, repoTimestamp);
receiver.receiveRepo(repoName, repoDescription, repoSigningCert, repoMaxAge, repoVersion,
repoTimestamp, repoIcon, repoMirrors.toArray(new String[repoMirrors.size()]));
}
private void onRepoPushRequestParsed(RepoPushRequest repoPushRequest) {
@ -331,6 +337,7 @@ public class RepoXMLHandler extends DefaultHandler {
repoName = cleanWhiteSpace(attributes.getValue("", "name"));
repoDescription = cleanWhiteSpace(attributes.getValue("", "description"));
repoTimestamp = parseLong(attributes.getValue("", "timestamp"), 0);
repoIcon = attributes.getValue("", "icon");
} else if (RepoPushRequest.INSTALL.equals(localName)
|| RepoPushRequest.UNINSTALL.equals(localName)) {
if (repo.pushRequests == Repo.PUSH_REQUEST_ACCEPT_ALWAYS) {

View File

@ -2,6 +2,7 @@ package org.fdroid.fdroid.data;
import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import android.database.Cursor;
import android.os.Build;
import android.os.Parcel;
@ -73,6 +74,8 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
public String repoAddress;
public String[] incompatibleReasons;
public String[] antiFeatures;
/**
* The numeric primary key of the Metadata table, which is used to join apks.
*/
@ -203,6 +206,9 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
case Cols.Repo.ADDRESS:
repoAddress = cursor.getString(i);
break;
case Cols.ANTI_FEATURES:
antiFeatures = Utils.parseCommaSeparatedString(cursor.getString(i));
break;
}
}
}
@ -303,6 +309,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
values.put(Cols.FEATURES, Utils.serializeCommaSeparatedString(features));
values.put(Cols.NATIVE_CODE, Utils.serializeCommaSeparatedString(nativecode));
values.put(Cols.INCOMPATIBLE_REASONS, Utils.serializeCommaSeparatedString(incompatibleReasons));
values.put(Cols.ANTI_FEATURES, Utils.serializeCommaSeparatedString(antiFeatures));
values.put(Cols.IS_COMPATIBLE, compatible ? 1 : 0);
return values;
}
@ -349,6 +356,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
dest.writeInt(this.repoVersion);
dest.writeString(this.repoAddress);
dest.writeStringArray(this.incompatibleReasons);
dest.writeStringArray(this.antiFeatures);
dest.writeLong(this.appId);
}
@ -380,6 +388,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
this.repoVersion = in.readInt();
this.repoAddress = in.readString();
this.incompatibleReasons = in.createStringArray();
this.antiFeatures = in.createStringArray();
this.appId = in.readLong();
}

View File

@ -65,22 +65,22 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
public String license = "Unknown";
public String author;
public String email;
public String authorName;
public String authorEmail;
public String webURL;
public String webSite;
public String trackerURL;
public String issueTracker;
public String sourceURL;
public String sourceCode;
public String changelogURL;
public String changelog;
public String donateURL;
public String donate;
public String bitcoinAddr;
public String bitcoin;
public String litecoinAddr;
public String litecoin;
public String flattrID;
@ -113,8 +113,9 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
public String[] antiFeatures;
/**
* List of special requirements (such as root privileges) or null if there aren't any.
* Requires root access (only ever used for root)
*/
@Deprecated
public String[] requirements;
private AppPrefs prefs;
@ -185,32 +186,32 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
case Cols.LICENSE:
license = cursor.getString(i);
break;
case Cols.AUTHOR:
author = cursor.getString(i);
case Cols.AUTHOR_NAME:
authorName = cursor.getString(i);
break;
case Cols.EMAIL:
email = cursor.getString(i);
case Cols.AUTHOR_EMAIL:
authorEmail = cursor.getString(i);
break;
case Cols.WEB_URL:
webURL = cursor.getString(i);
case Cols.WEBSITE:
webSite = cursor.getString(i);
break;
case Cols.TRACKER_URL:
trackerURL = cursor.getString(i);
case Cols.ISSUE_TRACKER:
issueTracker = cursor.getString(i);
break;
case Cols.SOURCE_URL:
sourceURL = cursor.getString(i);
case Cols.SOURCE_CODE:
sourceCode = cursor.getString(i);
break;
case Cols.CHANGELOG_URL:
changelogURL = cursor.getString(i);
case Cols.CHANGELOG:
changelog = cursor.getString(i);
break;
case Cols.DONATE_URL:
donateURL = cursor.getString(i);
case Cols.DONATE:
donate = cursor.getString(i);
break;
case Cols.BITCOIN_ADDR:
bitcoinAddr = cursor.getString(i);
case Cols.BITCOIN:
bitcoin = cursor.getString(i);
break;
case Cols.LITECOIN_ADDR:
litecoinAddr = cursor.getString(i);
case Cols.LITECOIN:
litecoin = cursor.getString(i);
break;
case Cols.FLATTR_ID:
flattrID = cursor.getString(i);
@ -489,15 +490,15 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
values.put(Cols.ICON_URL_LARGE, iconUrlLarge);
values.put(Cols.DESCRIPTION, description);
values.put(Cols.LICENSE, license);
values.put(Cols.AUTHOR, author);
values.put(Cols.EMAIL, email);
values.put(Cols.WEB_URL, webURL);
values.put(Cols.TRACKER_URL, trackerURL);
values.put(Cols.SOURCE_URL, sourceURL);
values.put(Cols.CHANGELOG_URL, changelogURL);
values.put(Cols.DONATE_URL, donateURL);
values.put(Cols.BITCOIN_ADDR, bitcoinAddr);
values.put(Cols.LITECOIN_ADDR, litecoinAddr);
values.put(Cols.AUTHOR_NAME, authorName);
values.put(Cols.AUTHOR_EMAIL, authorEmail);
values.put(Cols.WEBSITE, webSite);
values.put(Cols.ISSUE_TRACKER, issueTracker);
values.put(Cols.SOURCE_CODE, sourceCode);
values.put(Cols.CHANGELOG, changelog);
values.put(Cols.DONATE, donate);
values.put(Cols.BITCOIN, bitcoin);
values.put(Cols.LITECOIN, litecoin);
values.put(Cols.FLATTR_ID, flattrID);
values.put(Cols.ADDED, Utils.formatDate(added, ""));
values.put(Cols.LAST_UPDATED, Utils.formatDate(lastUpdated, ""));
@ -555,12 +556,12 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
@Nullable
public String getBitcoinUri() {
return TextUtils.isEmpty(bitcoinAddr) ? null : "bitcoin:" + bitcoinAddr;
return TextUtils.isEmpty(bitcoin) ? null : "bitcoin:" + bitcoin;
}
@Nullable
public String getLitecoinUri() {
return TextUtils.isEmpty(litecoinAddr) ? null : "litecoin:" + litecoinAddr;
return TextUtils.isEmpty(bitcoin) ? null : "litecoin:" + bitcoin;
}
@Nullable
@ -631,15 +632,15 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
dest.writeString(this.icon);
dest.writeString(this.description);
dest.writeString(this.license);
dest.writeString(this.author);
dest.writeString(this.email);
dest.writeString(this.webURL);
dest.writeString(this.trackerURL);
dest.writeString(this.sourceURL);
dest.writeString(this.changelogURL);
dest.writeString(this.donateURL);
dest.writeString(this.bitcoinAddr);
dest.writeString(this.litecoinAddr);
dest.writeString(this.authorName);
dest.writeString(this.authorEmail);
dest.writeString(this.webSite);
dest.writeString(this.issueTracker);
dest.writeString(this.sourceCode);
dest.writeString(this.changelog);
dest.writeString(this.donate);
dest.writeString(this.bitcoin);
dest.writeString(this.litecoin);
dest.writeString(this.flattrID);
dest.writeString(this.upstreamVersionName);
dest.writeInt(this.upstreamVersionCode);
@ -668,15 +669,15 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
this.icon = in.readString();
this.description = in.readString();
this.license = in.readString();
this.author = in.readString();
this.email = in.readString();
this.webURL = in.readString();
this.trackerURL = in.readString();
this.sourceURL = in.readString();
this.changelogURL = in.readString();
this.donateURL = in.readString();
this.bitcoinAddr = in.readString();
this.litecoinAddr = in.readString();
this.authorName = in.readString();
this.authorEmail = in.readString();
this.webSite = in.readString();
this.issueTracker = in.readString();
this.sourceCode = in.readString();
this.changelog = in.readString();
this.donate = in.readString();
this.bitcoin = in.readString();
this.litecoin = in.readString();
this.flattrID = in.readString();
this.upstreamVersionName = in.readString();
this.upstreamVersionCode = in.readInt();

View File

@ -12,9 +12,9 @@ import android.util.Log;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.ApkTable;
import org.fdroid.fdroid.data.Schema.AppPrefsTable;
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.fdroid.fdroid.data.Schema.AppPrefsTable;
import org.fdroid.fdroid.data.Schema.CatJoinTable;
import org.fdroid.fdroid.data.Schema.CategoryTable;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
@ -837,6 +837,11 @@ public class AppProvider extends FDroidProvider {
values.remove(Cols.Package.PACKAGE_NAME);
values.put(Cols.PACKAGE_ID, packageId);
if (!values.containsKey(Cols.DESCRIPTION) || values.getAsString(Cols.DESCRIPTION) == null) {
// the current structure assumes that description is always present and non-null
values.put(Cols.DESCRIPTION, "");
}
String[] categories = null;
boolean saveCategories = false;
if (values.containsKey(Cols.ForWriting.Categories.CATEGORIES)) {

View File

@ -76,6 +76,8 @@ class DBHelper extends SQLiteOpenHelper {
+ RepoTable.Cols.USERNAME + " string, "
+ RepoTable.Cols.PASSWORD + " string,"
+ RepoTable.Cols.TIMESTAMP + " integer not null default 0, "
+ RepoTable.Cols.ICON + " string, "
+ RepoTable.Cols.MIRRORS + " string, "
+ RepoTable.Cols.PUSH_REQUESTS + " integer not null default " + Repo.PUSH_REQUEST_IGNORE
+ ");";
@ -104,6 +106,7 @@ class DBHelper extends SQLiteOpenHelper {
+ ApkTable.Cols.ADDED_DATE + " string, "
+ ApkTable.Cols.IS_COMPATIBLE + " int not null, "
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text, "
+ ApkTable.Cols.ANTI_FEATURES + " string, "
+ "PRIMARY KEY (" + ApkTable.Cols.APP_ID + ", " + ApkTable.Cols.VERSION_CODE + ", " + ApkTable.Cols.REPO_ID + ")"
+ ");";
@ -116,19 +119,19 @@ class DBHelper extends SQLiteOpenHelper {
+ AppMetadataTable.Cols.ICON + " text, "
+ AppMetadataTable.Cols.DESCRIPTION + " text not null, "
+ AppMetadataTable.Cols.LICENSE + " text not null, "
+ AppMetadataTable.Cols.AUTHOR + " text, "
+ AppMetadataTable.Cols.EMAIL + " text, "
+ AppMetadataTable.Cols.WEB_URL + " text, "
+ AppMetadataTable.Cols.TRACKER_URL + " text, "
+ AppMetadataTable.Cols.SOURCE_URL + " text, "
+ AppMetadataTable.Cols.CHANGELOG_URL + " text, "
+ AppMetadataTable.Cols.AUTHOR_NAME + " text, "
+ AppMetadataTable.Cols.AUTHOR_EMAIL + " text, "
+ AppMetadataTable.Cols.WEBSITE + " text, "
+ AppMetadataTable.Cols.ISSUE_TRACKER + " text, "
+ AppMetadataTable.Cols.SOURCE_CODE + " text, "
+ AppMetadataTable.Cols.CHANGELOG + " text, "
+ AppMetadataTable.Cols.SUGGESTED_VERSION_CODE + " text,"
+ AppMetadataTable.Cols.UPSTREAM_VERSION_NAME + " text,"
+ AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + " integer,"
+ AppMetadataTable.Cols.ANTI_FEATURES + " string,"
+ AppMetadataTable.Cols.DONATE_URL + " string,"
+ AppMetadataTable.Cols.BITCOIN_ADDR + " string,"
+ AppMetadataTable.Cols.LITECOIN_ADDR + " string,"
+ AppMetadataTable.Cols.DONATE + " string,"
+ AppMetadataTable.Cols.BITCOIN + " string,"
+ AppMetadataTable.Cols.LITECOIN + " string,"
+ AppMetadataTable.Cols.FLATTR_ID + " string,"
+ AppMetadataTable.Cols.REQUIREMENTS + " string,"
+ AppMetadataTable.Cols.ADDED + " string,"
@ -178,7 +181,7 @@ class DBHelper extends SQLiteOpenHelper {
+ InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
+ " );";
protected static final int DB_VERSION = 65;
protected static final int DB_VERSION = 66;
private final Context context;
@ -258,6 +261,27 @@ class DBHelper extends SQLiteOpenHelper {
migrateToPackageTable(db, oldVersion);
addObbFiles(db, oldVersion);
addCategoryTables(db, oldVersion);
addIndexV1Fields(db, oldVersion);
}
private void addIndexV1Fields(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 66) {
return;
}
if (!columnExists(db, Schema.RepoTable.NAME, RepoTable.Cols.ICON)) {
Utils.debugLog(TAG, "Adding " + RepoTable.Cols.ICON + " field to " + RepoTable.NAME + " table in db.");
db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.ICON + " string;");
}
if (!columnExists(db, RepoTable.NAME, RepoTable.Cols.MIRRORS)) {
Utils.debugLog(TAG, "Adding " + RepoTable.Cols.MIRRORS + " field to " + RepoTable.NAME + " table in db.");
db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.MIRRORS + " string;");
}
if (!columnExists(db, ApkTable.NAME, ApkTable.Cols.ANTI_FEATURES)) {
Utils.debugLog(TAG, "Adding " + ApkTable.Cols.ANTI_FEATURES + " field to " + ApkTable.NAME + " table in db.");
db.execSQL("alter table " + ApkTable.NAME + " add column " + ApkTable.Cols.ANTI_FEATURES + " string;");
}
}
/**
@ -756,11 +780,11 @@ class DBHelper extends SQLiteOpenHelper {
}
private void addChangelogToApp(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 48 || columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.CHANGELOG_URL)) {
if (oldVersion >= 48 || columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.CHANGELOG)) {
return;
}
Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.CHANGELOG_URL + " column to " + AppMetadataTable.NAME);
db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.CHANGELOG_URL + " text");
Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.CHANGELOG + " column to " + AppMetadataTable.NAME);
db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.CHANGELOG + " text");
}
private void addIconUrlLargeToApp(SQLiteDatabase db, int oldVersion) {
@ -809,13 +833,13 @@ class DBHelper extends SQLiteOpenHelper {
if (oldVersion >= 53) {
return;
}
if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.AUTHOR)) {
Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.AUTHOR + " column to " + AppMetadataTable.NAME);
db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.AUTHOR + " text");
if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.AUTHOR_NAME)) {
Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.AUTHOR_NAME + " column to " + AppMetadataTable.NAME);
db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.AUTHOR_NAME + " text");
}
if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.EMAIL)) {
Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.EMAIL + " column to " + AppMetadataTable.NAME);
db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.EMAIL + " text");
if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.AUTHOR_EMAIL)) {
Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.AUTHOR_EMAIL + " column to " + AppMetadataTable.NAME);
db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.AUTHOR_EMAIL + " text");
}
}

View File

@ -124,13 +124,21 @@ public class InstalledAppProviderService extends IntentService {
* is in sync with what the {@link PackageManager} tells us is installed. Once
* completed, the relevant {@link android.content.ContentProvider}s will be
* notified of any changes to installed statuses.
* <p/>
* <p>
* The installed app cache could get out of sync, e.g. if F-Droid crashed/ or
* ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}.
* This method returns immediately, and will continue to work in an
* {@link IntentService}. It doesn't really matter where we put this in the
* bootstrap process, because it runs in its own thread, at the lowest priority:
* {@link Process#THREAD_PRIORITY_LOWEST}.
* <p>
* APKs installed in {@code /system} will often have zeroed out timestamps, like
* 2008-01-01 (ziptime) or 2009-01-01. So instead anything older than 2010 every
* time since we have no way to know whether an APK wasn't changed as part of an
* OTA update. An OTA update could change the APK without changing the
* {@link PackageInfo#versionCode} or {@link PackageInfo#lastUpdateTime}.
*
* @see <a href="https://gitlab.com/fdroid/fdroidclient/issues/819>issue #819</a>
*/
public static void compareToPackageManager(Context context) {
Map<String, Long> cachedInfo = InstalledAppProvider.Helper.all(context);
@ -139,7 +147,8 @@ public class InstalledAppProviderService extends IntentService {
.getInstalledPackages(PackageManager.GET_SIGNATURES);
for (PackageInfo packageInfo : packageInfoList) {
if (cachedInfo.containsKey(packageInfo.packageName)) {
if (packageInfo.lastUpdateTime > cachedInfo.get(packageInfo.packageName)) {
if (packageInfo.lastUpdateTime < 1262300400000L // 2010-01-01 00:00
|| packageInfo.lastUpdateTime > cachedInfo.get(packageInfo.packageName)) {
insert(context, packageInfo);
}
cachedInfo.remove(packageInfo.packageName);
@ -207,7 +216,7 @@ public class InstalledAppProviderService extends IntentService {
* broadcast. In the first case, it will already have a {@link PackageInfo} for us. However if
* it is from the later case, we'll need to query the {@link PackageManager} ourselves to get
* this info.
*
* <p>
* Can still return null, as there is potentially race conditions to do with uninstalling apps
* such that querying the {@link PackageManager} for a given package may throw an exception.
*/

View File

@ -47,6 +47,7 @@ public class Repo extends ValueObject {
public String address;
public String name;
public String description;
public String icon;
/** index version, i.e. what fdroidserver built it - 0 if not specified */
public int version;
public boolean inuse;
@ -71,6 +72,9 @@ public class Repo extends ValueObject {
/** When the signed repo index was generated, used to protect against replay attacks */
public long timestamp;
/** Official mirrors of this repo, considered automatically interchangeable */
public String[] mirrors;
/** How to treat push requests included in this repo's index XML */
public int pushRequests = PUSH_REQUEST_IGNORE;
@ -131,6 +135,12 @@ public class Repo extends ValueObject {
case Cols.TIMESTAMP:
timestamp = cursor.getLong(i);
break;
case Cols.ICON:
icon = cursor.getString(i);
break;
case Cols.MIRRORS:
mirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
break;
case Cols.PUSH_REQUESTS:
pushRequests = cursor.getInt(i);
break;
@ -257,6 +267,14 @@ public class Repo extends ValueObject {
timestamp = toInt(values.getAsInteger(Cols.TIMESTAMP));
}
if (values.containsKey(Cols.ICON)) {
icon = values.getAsString(Cols.ICON);
}
if (values.containsKey(Cols.MIRRORS)) {
mirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.MIRRORS));
}
if (values.containsKey(Cols.PUSH_REQUESTS)) {
pushRequests = toInt(values.getAsInteger(Cols.PUSH_REQUESTS));
}

View File

@ -124,15 +124,15 @@ public interface Schema {
String ICON = "icon";
String DESCRIPTION = "description";
String LICENSE = "license";
String AUTHOR = "author";
String EMAIL = "email";
String WEB_URL = "webURL";
String TRACKER_URL = "trackerURL";
String SOURCE_URL = "sourceURL";
String CHANGELOG_URL = "changelogURL";
String DONATE_URL = "donateURL";
String BITCOIN_ADDR = "bitcoinAddr";
String LITECOIN_ADDR = "litecoinAddr";
String AUTHOR_NAME = "author";
String AUTHOR_EMAIL = "email";
String WEBSITE = "webURL";
String ISSUE_TRACKER = "trackerURL";
String SOURCE_CODE = "sourceURL";
String CHANGELOG = "changelogURL";
String DONATE = "donateURL";
String BITCOIN = "bitcoinAddr";
String LITECOIN = "litecoinAddr";
String FLATTR_ID = "flattrID";
String SUGGESTED_VERSION_CODE = "suggestedVercode";
String UPSTREAM_VERSION_NAME = "upstreamVersion";
@ -176,8 +176,8 @@ public interface Schema {
*/
String[] ALL_COLS = {
ROW_ID, PACKAGE_ID, REPO_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION,
LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
LICENSE, AUTHOR_NAME, AUTHOR_EMAIL, WEBSITE, ISSUE_TRACKER, SOURCE_CODE,
CHANGELOG, DONATE, BITCOIN, LITECOIN, FLATTR_ID,
UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE,
SUGGESTED_VERSION_CODE,
@ -190,8 +190,8 @@ public interface Schema {
*/
String[] ALL = {
_ID, ROW_ID, REPO_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION,
LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
LICENSE, AUTHOR_NAME, AUTHOR_EMAIL, WEBSITE, ISSUE_TRACKER, SOURCE_CODE,
CHANGELOG, DONATE, BITCOIN, LITECOIN, FLATTR_ID,
UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE,
SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME,
@ -240,6 +240,7 @@ public interface Schema {
String ADDED_DATE = "added";
String IS_COMPATIBLE = "compatible";
String INCOMPATIBLE_REASONS = "incompatibleReasons";
String ANTI_FEATURES = "antiFeatures";
interface Repo {
String VERSION = "repoVersion";
@ -258,7 +259,7 @@ public interface Schema {
SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION,
OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256,
REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
IS_COMPATIBLE, INCOMPATIBLE_REASONS,
IS_COMPATIBLE, INCOMPATIBLE_REASONS, ANTI_FEATURES,
};
/**
@ -270,6 +271,7 @@ public interface Schema {
OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256,
REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS,
ANTI_FEATURES,
};
}
}
@ -295,12 +297,14 @@ public interface Schema {
String USERNAME = "username";
String PASSWORD = "password";
String TIMESTAMP = "timestamp";
String ICON = "icon";
String MIRRORS = "mirrors";
String PUSH_REQUESTS = "pushRequests";
String[] ALL = {
_ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, SIGNING_CERT,
FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP,
USERNAME, PASSWORD, TIMESTAMP, PUSH_REQUESTS,
USERNAME, PASSWORD, TIMESTAMP, ICON, MIRRORS, PUSH_REQUESTS,
};
}
}

View File

@ -176,7 +176,7 @@ public class AppDetailsRecyclerViewAdapter
}
private boolean shouldShowDonate() {
return uriIsSetAndCanBeOpened(app.donateURL) ||
return uriIsSetAndCanBeOpened(app.donate) ||
uriIsSetAndCanBeOpened(app.getBitcoinUri()) ||
uriIsSetAndCanBeOpened(app.getLitecoinUri()) ||
uriIsSetAndCanBeOpened(app.getFlattrUri());
@ -382,8 +382,8 @@ public class AppDetailsRecyclerViewAdapter
public void bindModel() {
ImageLoader.getInstance().displayImage(app.iconUrlLarge, iconView, displayImageOptions);
titleView.setText(app.name);
if (!TextUtils.isEmpty(app.author)) {
authorView.setText(context.getString(R.string.by_author) + " " + app.author);
if (!TextUtils.isEmpty(app.authorName)) {
authorView.setText(context.getString(R.string.by_author) + " " + app.authorName);
authorView.setVisibility(View.VISIBLE);
} else {
authorView.setVisibility(View.GONE);
@ -523,18 +523,18 @@ public class AppDetailsRecyclerViewAdapter
}
public void bindModel() {
if (TextUtils.isEmpty(app.author)) {
if (TextUtils.isEmpty(app.authorName)) {
donateHeading.setText(context.getString(R.string.app_details_donate_prompt_unknown_author, app.name));
} else {
String author = "<strong>" + app.author + "</strong>";
String author = "<strong>" + app.authorName + "</strong>";
donateHeading.setText(Html.fromHtml(context.getString(R.string.app_details_donate_prompt, app.name, author)));
}
donationOptionsLayout.removeAllViews();
// Donate button
if (uriIsSetAndCanBeOpened(app.donateURL)) {
addDonateOption(R.layout.donate_generic, app.donateURL);
if (uriIsSetAndCanBeOpened(app.donate)) {
addDonateOption(R.layout.donate_generic, app.donate);
}
// Bitcoin
@ -639,28 +639,28 @@ public class AppDetailsRecyclerViewAdapter
contentView.removeAllViews();
// Source button
if (uriIsSetAndCanBeOpened(app.sourceURL)) {
addLinkItemView(contentView, R.string.menu_source, R.drawable.ic_source_code, app.sourceURL);
if (uriIsSetAndCanBeOpened(app.sourceCode)) {
addLinkItemView(contentView, R.string.menu_source, R.drawable.ic_source_code, app.sourceCode);
}
// Issues button
if (uriIsSetAndCanBeOpened(app.trackerURL)) {
addLinkItemView(contentView, R.string.menu_issues, R.drawable.ic_issues, app.trackerURL);
if (uriIsSetAndCanBeOpened(app.issueTracker)) {
addLinkItemView(contentView, R.string.menu_issues, R.drawable.ic_issues, app.issueTracker);
}
// Changelog button
if (uriIsSetAndCanBeOpened(app.changelogURL)) {
addLinkItemView(contentView, R.string.menu_changelog, R.drawable.ic_changelog, app.changelogURL);
if (uriIsSetAndCanBeOpened(app.changelog)) {
addLinkItemView(contentView, R.string.menu_changelog, R.drawable.ic_changelog, app.changelog);
}
// Website button
if (uriIsSetAndCanBeOpened(app.webURL)) {
addLinkItemView(contentView, R.string.menu_website, R.drawable.ic_website, app.webURL);
if (uriIsSetAndCanBeOpened(app.webSite)) {
addLinkItemView(contentView, R.string.menu_website, R.drawable.ic_website, app.webSite);
}
// Email button
final String subject = Uri.encode(context.getString(R.string.app_details_subject, app.name));
String emailUrl = app.email == null ? null : ("mailto:" + app.email + "?subject=" + subject);
String emailUrl = app.authorEmail == null ? null : ("mailto:" + app.authorEmail + "?subject=" + subject);
if (uriIsSetAndCanBeOpened(emailUrl)) {
addLinkItemView(contentView, R.string.menu_email, R.drawable.ic_email, emailUrl);
}

View File

@ -274,9 +274,9 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
assertEquals("2048", a2048.name);
assertEquals(String.format("<p>2048 from %s repo.</p>", id), a2048.description);
assertEquals(String.format("Puzzle game (%s)", id), a2048.summary);
assertEquals(String.format("https://github.com/uberspot/2048-android?%s", id), a2048.webURL);
assertEquals(String.format("https://github.com/uberspot/2048-android?code&%s", id), a2048.sourceURL);
assertEquals(String.format("https://github.com/uberspot/2048-android/issues?%s", id), a2048.trackerURL);
assertEquals(String.format("https://github.com/uberspot/2048-android?%s", id), a2048.webSite);
assertEquals(String.format("https://github.com/uberspot/2048-android?code&%s", id), a2048.sourceCode);
assertEquals(String.format("https://github.com/uberspot/2048-android/issues?%s", id), a2048.issueTracker);
}
private void assertAdAwayMetadata(Repo repo, @RepoIdentifier String id) {
@ -290,11 +290,11 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
assertEquals(String.format("AdAway", id), adaway.name);
assertEquals(String.format("<p>AdAway from %s repo.</p>", id), adaway.description);
assertEquals(String.format("Block advertisements (%s)", id), adaway.summary);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.webURL);
assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id), adaway.sourceURL);
assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id), adaway.trackerURL);
assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id), adaway.changelogURL);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.donateURL);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.webSite);
assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id), adaway.sourceCode);
assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id), adaway.issueTracker);
assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id), adaway.changelog);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.donate);
assertEquals(String.format("369138", id), adaway.flattrID);
}
@ -309,9 +309,9 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
assertEquals("adbWireless", adb.name);
assertEquals(String.format("<p>adbWireless from %s repo.</p>", id), adb.description);
assertEquals(String.format("Wireless adb (%s)", id), adb.summary);
assertEquals(String.format("https://adbwireless.example.com?%s", id), adb.webURL);
assertEquals(String.format("https://adbwireless.example.com/source?%s", id), adb.sourceURL);
assertEquals(String.format("https://adbwireless.example.com/issues?%s", id), adb.trackerURL);
assertEquals(String.format("https://adbwireless.example.com?%s", id), adb.webSite);
assertEquals(String.format("https://adbwireless.example.com/source?%s", id), adb.sourceCode);
assertEquals(String.format("https://adbwireless.example.com/issues?%s", id), adb.issueTracker);
}
private void assertCalendarMetadata(Repo repo, @RepoIdentifier String id) {
@ -325,9 +325,9 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
assertEquals("Add to calendar", calendar.name);
assertEquals(String.format("<p>Add to calendar from %s repo.</p>", id), calendar.description);
assertEquals(String.format("Import .ics files into calendar (%s)", id), calendar.summary);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id), calendar.webURL);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id), calendar.sourceURL);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id), calendar.trackerURL);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id), calendar.webSite);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id), calendar.sourceCode);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id), calendar.issueTracker);
assertEquals("2225390", calendar.flattrID);
}

View File

@ -31,19 +31,24 @@ public class RepoDetails implements RepoXMLHandler.IndexReceiver {
public int maxAge;
public int version;
public long timestamp;
public String icon;
public String[] mirrors;
public List<Apk> apks = new ArrayList<>();
public List<App> apps = new ArrayList<>();
public List<RepoPushRequest> repoPushRequestList = new ArrayList<>();
@Override
public void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp) {
public void receiveRepo(String name, String description, String signingCert, int maxage,
int version, long timestamp, String icon, String[] mirrors) {
this.name = name;
this.description = description;
this.signingCert = signingCert;
this.maxAge = maxage;
this.version = version;
this.timestamp = timestamp;
this.icon = icon;
this.mirrors = mirrors;
}
@Override