Optimize repo update

This commit is contained in:
topjohnwu 2018-04-07 04:47:22 +08:00
parent b75018b03b
commit 684c5d225a

View File

@ -1,12 +1,10 @@
package com.topjohnwu.magisk.asyncs; package com.topjohnwu.magisk.asyncs;
import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.ReposFragment; import com.topjohnwu.magisk.ReposFragment;
import com.topjohnwu.magisk.container.Repo; import com.topjohnwu.magisk.container.Repo;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.utils.Const; import com.topjohnwu.magisk.utils.Const;
import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.WebService; import com.topjohnwu.magisk.utils.WebService;
@ -16,6 +14,7 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -24,55 +23,81 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class UpdateRepos extends ParallelTask<Void, Void, Void> { public class UpdateRepos extends ParallelTask<Void, Void, Void> {
private static final int CHECK_ETAG = 0; private static final int CHECK_ETAG = 0;
private static final int LOAD_NEXT = 1; private static final int LOAD_NEXT = 1;
private static final int LOAD_PREV = 2; private static final int LOAD_PREV = 2;
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
private MagiskManager mm;
private List<String> cached, etags, newEtags = new ArrayList<>(); private List<String> cached, etags, newEtags = new ArrayList<>();
private RepoDatabaseHelper repoDB;
private SharedPreferences prefs;
private boolean forceUpdate; private boolean forceUpdate;
private AtomicInteger taskCount = new AtomicInteger(0);
private int tasks = 0; final private Object allDone = new Object();
public UpdateRepos(boolean force) { public UpdateRepos(boolean force) {
MagiskManager mm = MagiskManager.get(); mm = MagiskManager.get();
prefs = mm.prefs;
repoDB = mm.repoDB;
mm.repoLoadDone.reset(); mm.repoLoadDone.reset();
// Legacy data cleanup // Legacy data cleanup
File old = new File(mm.getApplicationInfo().dataDir + "/shared_prefs", "RepoMap.xml"); File old = new File(mm.getApplicationInfo().dataDir + "/shared_prefs", "RepoMap.xml");
if (old.exists() || prefs.getString("repomap", null) != null) { if (old.exists() || mm.prefs.getString("repomap", null) != null) {
old.delete(); old.delete();
prefs.edit().remove("version").remove("repomap").remove(Const.Key.ETAG_KEY).apply(); mm.prefs.edit().remove("version").remove("repomap").remove(Const.Key.ETAG_KEY).apply();
repoDB.clearRepo(); mm.repoDB.clearRepo();
} }
forceUpdate = force; forceUpdate = force;
} }
private void queueTask(Runnable task) {
// Thread pool's queue has an upper bound, batch it with 64 tasks
while (taskCount.get() >= 64) {
waitTasks();
}
taskCount.incrementAndGet();
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
task.run();
if (taskCount.decrementAndGet() == 0) {
synchronized (allDone) {
allDone.notify();
}
}
});
}
private void waitTasks() {
if (taskCount.get() == 0)
return;
synchronized (allDone) {
try {
allDone.wait();
} catch (InterruptedException e) {
// Wait again
waitTasks();
}
}
}
private void loadJSON(String jsonString) throws Exception { private void loadJSON(String jsonString) throws Exception {
JSONArray jsonArray = new JSONArray(jsonString); JSONArray jsonArray = new JSONArray(jsonString);
// Empty page, throw error // Empty page, throw error
if (jsonArray.length() == 0) throw new Exception(); if (jsonArray.length() == 0)
throw new Exception();
for (int i = 0; i < jsonArray.length(); i++) { for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonobject = jsonArray.getJSONObject(i); JSONObject rawRepo = jsonArray.getJSONObject(i);
String id = jsonobject.getString("description"); String id = rawRepo.getString("description");
String name = jsonobject.getString("name"); String name = rawRepo.getString("name");
String lastUpdate = jsonobject.getString("pushed_at"); Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); queueTask(() -> {
Date updatedDate = format.parse(lastUpdate); Repo repo = mm.repoDB.getRepo(id);
++tasks;
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
Repo repo = repoDB.getRepo(id);
Boolean updated; Boolean updated;
try { try {
if (repo == null) { if (repo == null) {
repo = new Repo(name, updatedDate); repo = new Repo(name, date);
updated = true; updated = true;
} else { } else {
// Popout from cached // Popout from cached
@ -81,11 +106,11 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
repo.update(); repo.update();
updated = true; updated = true;
} else { } else {
updated = repo.update(updatedDate); updated = repo.update(date);
} }
} }
if (updated) { if (updated) {
repoDB.addRepo(repo); mm.repoDB.addRepo(repo);
publishProgress(); publishProgress();
} }
if (!id.equals(repo.getId())) { if (!id.equals(repo.getId())) {
@ -93,9 +118,8 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
} }
} catch (Repo.IllegalRepoException e) { } catch (Repo.IllegalRepoException e) {
Logger.error(e.getMessage()); Logger.error(e.getMessage());
repoDB.removeRepo(id); mm.repoDB.removeRepo(id);
} }
--tasks;
}); });
} }
} }
@ -111,7 +135,8 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
HttpURLConnection conn = WebService.request(url, header); HttpURLConnection conn = WebService.request(url, header);
try { try {
if (conn == null) throw new Exception(); if (conn == null)
throw new Exception();
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
newEtags.add(etag); newEtags.add(etag);
return page + 1 < etags.size() && loadPage(page + 1, CHECK_ETAG); return page + 1 < etags.size() && loadPage(page + 1, CHECK_ETAG);
@ -143,17 +168,6 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
return true; return true;
} }
private Void waitTasks() {
while (tasks > 0) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override @Override
protected void onProgressUpdate(Void... values) { protected void onProgressUpdate(Void... values) {
if (ReposFragment.adapter != null) if (ReposFragment.adapter != null)
@ -162,55 +176,52 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
MagiskManager.get().repoLoadDone.setPending(); mm.repoLoadDone.setPending();
} }
@Override @Override
protected Void doInBackground(Void... voids) { protected Void doInBackground(Void... voids) {
etags = new ArrayList<>(Arrays.asList(prefs.getString(Const.Key.ETAG_KEY, "").split(","))); etags = new ArrayList<>(Arrays.asList(mm.prefs.getString(Const.Key.ETAG_KEY, "").split(",")));
cached = repoDB.getRepoIDList(); cached = mm.repoDB.getRepoIDList();
if (!loadPage(0, CHECK_ETAG)) { if (!loadPage(0, CHECK_ETAG)) {
// Nothing changed online // Nothing changed online
if (forceUpdate) { if (forceUpdate) {
for (String id : cached) { for (String id : cached) {
if (id == null) continue; if (id == null) continue;
++tasks; queueTask(() -> {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { Repo repo = mm.repoDB.getRepo(id);
Repo repo = repoDB.getRepo(id);
try { try {
repo.update(); repo.update();
repoDB.addRepo(repo); mm.repoDB.addRepo(repo);
} catch (Repo.IllegalRepoException e) { } catch (Repo.IllegalRepoException e) {
Logger.error(e.getMessage()); Logger.error(e.getMessage());
repoDB.removeRepo(repo); mm.repoDB.removeRepo(repo);
} }
--tasks;
}); });
} }
} }
return waitTasks(); waitTasks();
} else {
waitTasks();
// The leftover cached means they are removed from online repo
mm.repoDB.removeRepo(cached);
// Update ETag
StringBuilder etagBuilder = new StringBuilder();
for (int i = 0; i < newEtags.size(); ++i) {
if (i != 0) etagBuilder.append(",");
etagBuilder.append(newEtags.get(i));
}
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etagBuilder.toString()).apply();
} }
// Wait till all tasks are done
waitTasks();
// The leftover cached means they are removed from online repo
repoDB.removeRepo(cached);
// Update ETag
StringBuilder etagBuilder = new StringBuilder();
for (int i = 0; i < newEtags.size(); ++i) {
if (i != 0) etagBuilder.append(",");
etagBuilder.append(newEtags.get(i));
}
prefs.edit().putString(Const.Key.ETAG_KEY, etagBuilder.toString()).apply();
return null; return null;
} }
@Override @Override
protected void onPostExecute(Void v) { protected void onPostExecute(Void v) {
MagiskManager.get().repoLoadDone.publish(); mm.repoLoadDone.publish();
super.onPostExecute(v); super.onPostExecute(v);
} }
} }