Remove GSON and switch to database

This commit is contained in:
topjohnwu 2017-02-12 23:26:30 +08:00
parent 1418ec2416
commit 44b0d4127c
13 changed files with 162 additions and 90 deletions

View File

@ -51,9 +51,7 @@ dependencies {
compile 'com.android.support:cardview-v7:25.1.1'
compile 'com.android.support:design:25.1.1'
compile 'com.android.support:support-v4:25.1.1'
compile 'com.android.support:support-v13:25.1.1'
compile 'com.jakewharton:butterknife:8.5.1'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.github.clans:fab:1.6.4'
compile 'com.thoughtbot:expandablerecyclerview:1.4'
compile 'com.madgag.spongycastle:core:1.54.0.0'

View File

@ -16,27 +16,6 @@
# public *;
#}
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.topjohnwu.magisk.module.** { *; }
-keep class com.topjohnwu.magisk.module.ModuleHelper$ValueSortedMap { *; }
# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-keep class android.support.v7.internal.** { *; }
-keep interface android.support.v7.internal.** { *; }
-keep class android.support.v7.** { *; }

View File

@ -13,7 +13,7 @@ import android.widget.TextView;
import com.topjohnwu.magisk.adapters.SuLogAdapter;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.superuser.SuLogDatabaseHelper;
import com.topjohnwu.magisk.database.SuLogDatabaseHelper;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.List;

View File

@ -11,8 +11,8 @@ import android.widget.TextView;
import com.topjohnwu.magisk.adapters.PolicyAdapter;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.superuser.SuDatabaseHelper;
import java.util.List;

View File

@ -15,8 +15,8 @@ import android.widget.Switch;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.superuser.SuDatabaseHelper;
import com.topjohnwu.magisk.utils.Utils;
import java.util.HashSet;

View File

@ -2,10 +2,9 @@ package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.module.BaseModule;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.Logger;
@ -16,6 +15,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -26,13 +26,14 @@ import java.util.Map;
public class LoadRepos extends ParallelTask<Void, Void, Void> {
public static final String ETAG_KEY = "ETag";
public static final String VERSION_KEY = "version";
public static final String REPO_KEY = "repomap";
public static final String FILE_KEY = "RepoMap";
private static final int GSON_DB_VER = 1;
private static final String REPO_URL = "https://api.github.com/orgs/Magisk-Modules-Repo/repos";
private String prefsPath;
public LoadRepos(Activity context) {
super(context);
prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs";
}
@Override
@ -41,39 +42,27 @@ public class LoadRepos extends ParallelTask<Void, Void, Void> {
SharedPreferences prefs = magiskManager.prefs;
magiskManager.repoMap = new ValueSortedMap<>();
Gson gson = new Gson();
String jsonString;
int cachedVersion = prefs.getInt(VERSION_KEY, 0);
if (cachedVersion != GSON_DB_VER) {
// Ignore incompatible cached database
jsonString = null;
} else {
jsonString = prefs.getString(REPO_KEY, null);
}
Map<String, Repo> cached = null;
if (jsonString != null) {
cached = gson.fromJson(jsonString, new TypeToken<ValueSortedMap<String, Repo>>(){}.getType());
}
if (cached == null) {
cached = new ValueSortedMap<>();
}
// Legacy data cleanup
new File(prefsPath, "RepoMap.xml").delete();
prefs.edit().remove("version").remove("repomap").apply();
Map<String, String> header = new HashMap<>();
// Get cached ETag to add in the request header
String etag = prefs.getString(ETAG_KEY, "");
Map<String, String> header = new HashMap<>();
header.put("If-None-Match", etag);
// Making a request to main URL for repo info
jsonString = WebService.request(
magiskManager.getString(R.string.url_main), WebService.GET, null, header, false);
// Add header only if db exists
if (magiskManager.getDatabasePath("repo.db").exists())
header.put("If-None-Match", etag);
if (!jsonString.isEmpty()) {
magiskManager.repoMap = new ValueSortedMap<>();
// Make a request to main URL for repo info
String jsonString = WebService.request(REPO_URL, WebService.GET, null, header, false);
RepoDatabaseHelper dbHelper = new RepoDatabaseHelper(magiskManager);
ValueSortedMap<String, Repo> cached = dbHelper.getRepoMap();
if (!TextUtils.isEmpty(jsonString)) {
try {
JSONArray jsonArray = new JSONArray(jsonString);
// If it gets to this point, the response is valid, update ETag
@ -98,16 +87,18 @@ public class LoadRepos extends ParallelTask<Void, Void, Void> {
try {
if (repo == null) {
Logger.dev("LoadRepos: Create new repo " + id);
repo = new Repo(magiskManager, name, updatedDate);
repo = new Repo(name, updatedDate);
} else {
// Popout from cached
cached.remove(id);
Logger.dev("LoadRepos: Update cached repo " + id);
repo.update(updatedDate);
}
if (repo.getId() != null) {
if (repo.getId() != null)
magiskManager.repoMap.put(id, repo);
}
} catch (BaseModule.CacheModException ignored) {}
}
} catch (JSONException e) {
e.printStackTrace();
}
@ -115,13 +106,15 @@ public class LoadRepos extends ParallelTask<Void, Void, Void> {
// Use cached if no internet or no updates
Logger.dev("LoadRepos: No updates, use cached");
magiskManager.repoMap.putAll(cached);
cached.clear();
}
prefs.edit()
.putInt(VERSION_KEY, GSON_DB_VER)
.putString(REPO_KEY, gson.toJson(magiskManager.repoMap))
.putString(ETAG_KEY, etag)
.apply();
// Update the database
dbHelper.addRepoMap(magiskManager.repoMap);
// The leftover cached are those removed remote, cleanup db
dbHelper.removeRepo(cached);
// Update ETag
prefs.edit().putString(ETAG_KEY, etag).apply();
Logger.dev("LoadRepos: Repo load done");
return null;

View File

@ -0,0 +1,74 @@
package com.topjohnwu.magisk.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.ValueSortedMap;
import java.util.Collection;
public class RepoDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 1;
private static final String TABLE_NAME = "repos";
public RepoDatabaseHelper(Context context) {
super(context, "repo.db", null, DATABASE_VER);
}
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, DATABASE_VER);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion == 0) {
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
"(id TEXT, name TEXT, version TEXT, versionCode INT, " +
"author TEXT, description TEXT, repo_name TEXT, last_update INT, " +
"PRIMARY KEY(id))");
}
// No upgrades yet
}
public void addRepoMap(ValueSortedMap<String, Repo> map) {
SQLiteDatabase db = getWritableDatabase();
Collection<Repo> list = map.values();
for (Repo repo : list)
db.replace(TABLE_NAME, null, repo.getContentValues());
db.close();
}
public void clearRepo() {
SQLiteDatabase db = getWritableDatabase();
db.delete(TABLE_NAME, null, null);
db.close();
}
public void removeRepo(ValueSortedMap<String, Repo> map) {
SQLiteDatabase db = getWritableDatabase();
Collection<Repo> list = map.values();
for (Repo repo : list)
db.delete(TABLE_NAME, "id=?", new String[] { repo.getId() });
db.close();
}
public ValueSortedMap<String, Repo> getRepoMap() {
ValueSortedMap<String, Repo> ret = new ValueSortedMap<>();
SQLiteDatabase db = getReadableDatabase();
Repo repo;
try (Cursor c = db.query(TABLE_NAME, null, null, null, null, null, null)) {
while (c.moveToNext()) {
repo = new Repo(c);
ret.put(repo.getId(), repo);
}
}
db.close();
return ret;
}
}

View File

@ -1,4 +1,4 @@
package com.topjohnwu.magisk.superuser;
package com.topjohnwu.magisk.database;
import android.content.Context;
import android.content.pm.PackageManager;
@ -6,6 +6,8 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.superuser.Policy;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,4 +1,4 @@
package com.topjohnwu.magisk.superuser;
package com.topjohnwu.magisk.database;
import android.content.Context;
import android.database.Cursor;
@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,34 +1,47 @@
package com.topjohnwu.magisk.module;
import android.content.Context;
import android.content.ContentValues;
import android.database.Cursor;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.WebService;
import java.util.Date;
public class Repo extends BaseModule {
private String mLogUrl, mManifestUrl, mZipUrl;
private static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
private static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
private String repoName;
private Date mLastUpdate;
public Repo(Context context, String name, Date lastUpdate) throws CacheModException {
public Repo(String name, Date lastUpdate) throws CacheModException {
mLastUpdate = lastUpdate;
mLogUrl = context.getString(R.string.file_url, name, "changelog.txt");
mManifestUrl = context.getString(R.string.file_url, name, "module.prop");
mZipUrl = context.getString(R.string.zip_url, name);
repoName = name;
update();
}
public Repo(Cursor c) {
mId = c.getString(c.getColumnIndex("id"));
mName = c.getString(c.getColumnIndex("name"));
mVersion = c.getString(c.getColumnIndex("version"));
mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
mAuthor = c.getString(c.getColumnIndex("author"));
mDescription = c.getString(c.getColumnIndex("description"));
repoName = c.getString(c.getColumnIndex("repo_name"));
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
}
public void update() throws CacheModException {
Logger.dev("Repo: Re-fetch prop");
String props = WebService.request(mManifestUrl, WebService.GET, true);
String props = WebService.request(getManifestUrl(), WebService.GET, true);
String lines[] = props.split("\\n");
parseProps(lines);
}
public void update(Date lastUpdate) throws CacheModException {
Logger.dev("Repo: Old: " + mLastUpdate + " New: " + lastUpdate);
Logger.dev("Repo: Local: " + mLastUpdate + " Remote: " + lastUpdate);
if (mIsCacheModule)
throw new CacheModException(mId);
if (lastUpdate.after(mLastUpdate)) {
@ -37,16 +50,29 @@ public class Repo extends BaseModule {
}
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("id", mId);
values.put("name", mName);
values.put("version", mVersion);
values.put("versionCode", mVersionCode);
values.put("author", mAuthor);
values.put("description", mDescription);
values.put("repo_name", repoName);
values.put("last_update", mLastUpdate.getTime());
return values;
}
public String getZipUrl() {
return mZipUrl;
return String.format(ZIP_URL, repoName);
}
public String getLogUrl() {
return mLogUrl;
return String.format(FILE_URL, repoName, "changelog.txt");
}
public String getManifestUrl() {
return mManifestUrl;
return String.format(FILE_URL, repoName, "module.prop");
}
public Date getLastUpdate() {

View File

@ -8,6 +8,8 @@ import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.database.SuLogDatabaseHelper;
import java.util.Date;

View File

@ -21,6 +21,7 @@ import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.utils.CallbackEvent;
import java.io.DataInputStream;

View File

@ -17,6 +17,7 @@ import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.LoadRepos;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import java.io.File;
@ -157,13 +158,8 @@ public class Utils {
public static void clearRepoCache(Activity activity) {
MagiskManager magiskManager = getMagiskManager(activity);
SharedPreferences repoMap = activity.getSharedPreferences(LoadRepos.FILE_KEY, Context.MODE_PRIVATE);
repoMap.edit()
.remove(LoadRepos.ETAG_KEY)
.remove(LoadRepos.VERSION_KEY)
.apply();
magiskManager.repoLoadDone.isTriggered = false;
new LoadRepos(activity).exec();
magiskManager.prefs.edit().remove(LoadRepos.ETAG_KEY).apply();
new RepoDatabaseHelper(activity).clearRepo();
Toast.makeText(activity, R.string.repo_cache_cleared, Toast.LENGTH_SHORT).show();
}
}