Migrate account and instance data to SQLite

This commit is contained in:
Grishka 2024-09-15 20:15:01 +03:00
parent ee1a0cc666
commit 192c634755
6 changed files with 294 additions and 123 deletions

View File

@ -13,7 +13,7 @@ android {
applicationId "org.joinmastodon.android"
minSdk 23
targetSdk 34
versionCode 112
versionCode 114
versionName "2.6.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@ -27,10 +27,8 @@ import org.joinmastodon.android.model.Status;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumSet;
@ -43,7 +41,7 @@ import me.grishka.appkit.utils.WorkerThread;
public class CacheController{
private static final String TAG="CacheController";
private static final int DB_VERSION=3;
private static final int DB_VERSION=4;
public static final WorkerThread databaseThread=new WorkerThread("databaseThread");
public static final Handler uiHandler=new Handler(Looper.getMainLooper());
@ -320,7 +318,7 @@ public class CacheController{
lists=result;
if(callback!=null)
callback.onSuccess(result);
writeListsToFile();
writeLists();
}
@Override
@ -332,26 +330,22 @@ public class CacheController{
.exec(accountID);
}
private List<FollowList> loadListsFromFile(){
File file=getListsFile();
if(!file.exists())
return null;
try(InputStreamReader in=new InputStreamReader(new FileInputStream(file))){
return MastodonAPIController.gson.fromJson(in, new TypeToken<List<FollowList>>(){}.getType());
}catch(Exception x){
Log.w(TAG, "failed to read lists from cache file", x);
return null;
private List<FollowList> loadLists(){
SQLiteDatabase db=getOrOpenDatabase();
try(Cursor cursor=db.query("misc", new String[]{"value"}, "`key`=?", new String[]{"lists"}, null, null, null)){
if(!cursor.moveToFirst())
return null;
return MastodonAPIController.gson.fromJson(cursor.getString(0), new TypeToken<List<FollowList>>(){}.getType());
}
}
private void writeListsToFile(){
databaseThread.postRunnable(()->{
try(OutputStreamWriter out=new OutputStreamWriter(new FileOutputStream(getListsFile()))){
MastodonAPIController.gson.toJson(lists, out);
}catch(IOException x){
Log.w(TAG, "failed to write lists to cache file", x);
}
}, 0);
private void writeLists(){
runOnDbThread(db->{
ContentValues values=new ContentValues();
values.put("key", "lists");
values.put("value", MastodonAPIController.gson.toJson(lists));
db.insertWithOnConflict("misc", null, values, SQLiteDatabase.CONFLICT_REPLACE);
});
}
public void getLists(Callback<List<FollowList>> callback){
@ -361,7 +355,7 @@ public class CacheController{
return;
}
databaseThread.postRunnable(()->{
List<FollowList> lists=loadListsFromFile();
List<FollowList> lists=loadLists();
if(lists!=null){
this.lists=lists;
if(callback!=null)
@ -372,23 +366,19 @@ public class CacheController{
}, 0);
}
public File getListsFile(){
return new File(MastodonApp.context.getFilesDir(), "lists_"+accountID+".json");
}
public void addList(FollowList list){
if(lists==null)
return;
lists.add(list);
lists.sort(Comparator.comparing(l->l.title));
writeListsToFile();
writeLists();
}
public void deleteList(String id){
if(lists==null)
return;
lists.removeIf(l->l.id.equals(id));
writeListsToFile();
writeLists();
}
public void updateList(FollowList list){
@ -398,7 +388,7 @@ public class CacheController{
if(lists.get(i).id.equals(list.id)){
lists.set(i, list);
lists.sort(Comparator.comparing(l->l.title));
writeListsToFile();
writeLists();
break;
}
}
@ -436,6 +426,7 @@ public class CacheController{
`time` INTEGER NOT NULL
)""");
createRecentSearchesTable(db);
createMiscTable(db);
}
@Override
@ -446,6 +437,9 @@ public class CacheController{
if(oldVersion<3){
addTimeColumns(db);
}
if(oldVersion<4){
createMiscTable(db);
}
}
private void createRecentSearchesTable(SQLiteDatabase db){
@ -465,5 +459,13 @@ public class CacheController{
db.execSQL("ALTER TABLE `notifications_all` ADD `time` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `notifications_mentions` ADD `time` INTEGER NOT NULL DEFAULT 0");
}
private void createMiscTable(SQLiteDatabase db){
db.execSQL("""
CREATE TABLE `misc` (
`key` TEXT NOT NULL PRIMARY KEY,
`value` TEXT
)""");
}
}
}

View File

@ -146,7 +146,7 @@ public class PushSubscriptionManager{
session.pushPublicKey=Base64.encodeToString(publicKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
session.pushAuthKey=encodedAuthKey=Base64.encodeToString(authKey, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
session.pushAccountID=pushAccountID=Base64.encodeToString(randomAccountID, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
AccountSessionManager.getInstance().writeAccountsFile();
AccountSessionManager.getInstance().writeAccountPushSettings(accountID);
}catch(NoSuchAlgorithmException|InvalidAlgorithmParameterException e){
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
return;
@ -165,7 +165,7 @@ public class PushSubscriptionManager{
if(session==null)
return;
session.pushSubscription=result;
AccountSessionManager.getInstance().writeAccountsFile();
AccountSessionManager.getInstance().writeAccountPushSettings(accountID);
Log.d(TAG, "Successfully registered "+accountID+" for push notifications");
});
}
@ -191,7 +191,7 @@ public class PushSubscriptionManager{
result.policy=subscription.policy;
session.pushSubscription=result;
session.needUpdatePushSettings=false;
AccountSessionManager.getInstance().writeAccountsFile();
AccountSessionManager.getInstance().writeAccountPushSettings(accountID);
}
@Override
@ -204,7 +204,7 @@ public class PushSubscriptionManager{
return;
session.needUpdatePushSettings=true;
session.pushSubscription=subscription;
AccountSessionManager.getInstance().writeAccountsFile();
AccountSessionManager.getInstance().writeAccountPushSettings(accountID);
}
}
})

View File

@ -1,11 +1,16 @@
package org.joinmastodon.android.api.session;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
@ -13,6 +18,7 @@ import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.StatusInteractionController;
import org.joinmastodon.android.api.gson.JsonObjectBuilder;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentialsPreferences;
import org.joinmastodon.android.api.requests.markers.GetMarkers;
@ -47,6 +53,9 @@ public class AccountSession{
private static final String TAG="AccountSession";
private static final int MIN_DAYS_ACCOUNT_AGE_FOR_DONATIONS=28;
public static final int FLAG_ACTIVATED=1;
public static final int FLAG_NEED_UPDATE_PUSH_SETTINGS=1 << 1;
public Token token;
public Account self;
public String domain;
@ -70,7 +79,6 @@ public class AccountSession{
private transient SharedPreferences prefs;
private transient boolean preferencesNeedSaving;
private transient AccountLocalPreferences localPreferences;
private transient List<FollowList> lists;
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
this.token=token;
@ -84,6 +92,62 @@ public class AccountSession{
AccountSession(){}
AccountSession(ContentValues values){
domain=values.getAsString("domain");
self=MastodonAPIController.gson.fromJson(values.getAsString("account_obj"), Account.class);
token=MastodonAPIController.gson.fromJson(values.getAsString("token"), Token.class);
app=MastodonAPIController.gson.fromJson(values.getAsString("application"), Application.class);
infoLastUpdated=values.getAsLong("info_last_updated");
long flags=values.getAsLong("flags");
activated=(flags & FLAG_ACTIVATED)==FLAG_ACTIVATED;
needUpdatePushSettings=(flags & FLAG_NEED_UPDATE_PUSH_SETTINGS)==FLAG_NEED_UPDATE_PUSH_SETTINGS;
JsonObject pushKeys=JsonParser.parseString(values.getAsString("push_keys")).getAsJsonObject();
pushAuthKey=pushKeys.get("auth").getAsString();
pushPrivateKey=pushKeys.get("private").getAsString();
pushPublicKey=pushKeys.get("public").getAsString();
pushSubscription=MastodonAPIController.gson.fromJson(values.getAsString("push_subscription"), PushSubscription.class);
JsonObject legacyFilters=JsonParser.parseString(values.getAsString("legacy_filters")).getAsJsonObject();
wordFilters=MastodonAPIController.gson.fromJson(legacyFilters.getAsJsonArray("filters"), new TypeToken<List<LegacyFilter>>(){}.getType());
filtersLastUpdated=legacyFilters.get("updated").getAsLong();
pushAccountID=values.getAsString("push_id");
activationInfo=MastodonAPIController.gson.fromJson(values.getAsString("activation_info"), AccountActivationInfo.class);
preferences=MastodonAPIController.gson.fromJson(values.getAsString("preferences"), Preferences.class);
}
public void toContentValues(ContentValues values){
values.put("id", getID());
values.put("domain", domain.toLowerCase());
values.put("account_obj", MastodonAPIController.gson.toJson(self));
values.put("token", MastodonAPIController.gson.toJson(token));
values.put("application", MastodonAPIController.gson.toJson(app));
values.put("info_last_updated", infoLastUpdated);
values.put("flags", getFlagsForDatabase());
values.put("push_keys", new JsonObjectBuilder()
.add("auth", pushAuthKey)
.add("private", pushPrivateKey)
.add("public", pushPublicKey)
.build()
.toString());
values.put("push_subscription", MastodonAPIController.gson.toJson(pushSubscription));
values.put("legacy_filters", new JsonObjectBuilder()
.add("filters", MastodonAPIController.gson.toJsonTree(wordFilters))
.add("updated", filtersLastUpdated)
.build()
.toString());
values.put("push_id", pushAccountID);
values.put("activation_info", MastodonAPIController.gson.toJson(activationInfo));
values.put("preferences", MastodonAPIController.gson.toJson(preferences));
}
public long getFlagsForDatabase(){
long flags=0;
if(activated)
flags|=FLAG_ACTIVATED;
if(needUpdatePushSettings)
flags|=FLAG_NEED_UPDATE_PUSH_SETTINGS;
return flags;
}
public String getID(){
return domain+"_"+self.id;
}
@ -124,7 +188,7 @@ public class AccountSession{
preferences=result;
if(callback!=null)
callback.accept(result);
AccountSessionManager.getInstance().writeAccountsFile();
AccountSessionManager.getInstance().updateAccountPreferences(getID(), result);
}
@Override
@ -206,7 +270,7 @@ public class AccountSession{
public void onSuccess(Account result){
preferencesNeedSaving=false;
self=result;
AccountSessionManager.getInstance().writeAccountsFile();
AccountSessionManager.getInstance().updateAccountInfo(getID(), self);
}
@Override

View File

@ -10,6 +10,7 @@ import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
@ -18,6 +19,11 @@ import android.net.Uri;
import android.os.Build;
import android.util.Log;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MainActivity;
@ -27,6 +33,7 @@ import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.DatabaseRunnable;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.gson.JsonObjectBuilder;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.filters.GetLegacyFilters;
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
@ -39,15 +46,14 @@ import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
@ -68,7 +74,7 @@ public class AccountSessionManager{
private static final String TAG="AccountSessionManager";
public static final String SCOPE="read write follow push";
public static final String REDIRECT_URI="mastodon-android-auth://callback";
private static final int DB_VERSION=1;
private static final int DB_VERSION=2;
private static final AccountSessionManager instance=new AccountSessionManager();
@ -84,6 +90,7 @@ public class AccountSessionManager{
private boolean loadedInstances;
private DatabaseHelper db;
private final Runnable databaseCloseRunnable=this::closeDatabase;
private final Object databaseLock=new Object();
public static AccountSessionManager getInstance(){
return instance;
@ -91,21 +98,20 @@ public class AccountSessionManager{
private AccountSessionManager(){
prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE);
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
if(!file.exists())
return;
HashSet<String> domains=new HashSet<>();
try(FileInputStream in=new FileInputStream(file)){
SessionsStorageWrapper w=MastodonAPIController.gson.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), SessionsStorageWrapper.class);
for(AccountSession session:w.accounts){
domains.add(session.domain.toLowerCase());
sessions.put(session.getID(), session);
runWithDatabase(db->{
HashSet<String> domains=new HashSet<>();
try(Cursor cursor=db.query("accounts", null, null, null, null, null, null)){
ContentValues values=new ContentValues();
while(cursor.moveToNext()){
DatabaseUtils.cursorRowToContentValues(cursor, values);
AccountSession session=new AccountSession(values);
domains.add(session.domain.toLowerCase());
sessions.put(session.getID(), session);
}
}
}catch(Exception x){
Log.e(TAG, "Error loading accounts", x);
}
readInstanceInfo(db, domains);
});
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
readInstanceInfo(domains);
maybeUpdateShortcuts();
}
@ -114,7 +120,11 @@ public class AccountSessionManager{
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
sessions.put(session.getID(), session);
lastActiveAccountID=session.getID();
writeAccountsFile();
runOnDbThread(db->{
ContentValues values=new ContentValues();
session.toContentValues(values);
db.insertWithOnConflict("accounts", null, values, SQLiteDatabase.CONFLICT_REPLACE);
});
updateInstanceEmojis(instance, instance.uri);
if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null);
@ -122,22 +132,6 @@ public class AccountSessionManager{
maybeUpdateShortcuts();
}
public synchronized void writeAccountsFile(){
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
try{
try(FileOutputStream out=new FileOutputStream(file)){
SessionsStorageWrapper w=new SessionsStorageWrapper();
w.accounts=new ArrayList<>(sessions.values());
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
MastodonAPIController.gson.toJson(w, writer);
writer.flush();
}
}catch(IOException x){
Log.e(TAG, "Error writing accounts file", x);
}
prefs.edit().putString("lastActiveAccount", lastActiveAccountID).apply();
}
@NonNull
public List<AccountSession> getLoggedInAccounts(){
return new ArrayList<>(sessions.values());
@ -167,7 +161,7 @@ public class AccountSessionManager{
if(!sessions.containsKey(lastActiveAccountID)){
// TODO figure out why this happens. It should not be possible.
lastActiveAccountID=getLoggedInAccounts().get(0).getID();
writeAccountsFile();
prefs.edit().putString("lastActiveAccount", lastActiveAccountID).apply();
}
return getAccount(lastActiveAccountID);
}
@ -186,7 +180,6 @@ public class AccountSessionManager{
public void removeAccount(String id){
AccountSession session=getAccount(id);
session.getCacheController().closeDatabase();
session.getCacheController().getListsFile().delete();
MastodonApp.context.deleteDatabase(id+".db");
MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
@ -206,11 +199,10 @@ public class AccountSessionManager{
lastActiveAccountID=getLoggedInAccounts().get(0).getID();
prefs.edit().putString("lastActiveAccount", lastActiveAccountID).apply();
}
writeAccountsFile();
String domain=session.domain.toLowerCase();
if(sessions.isEmpty() || !sessions.values().stream().map(s->s.domain.toLowerCase()).collect(Collectors.toSet()).contains(domain)){
getInstanceInfoFile(domain).delete();
}
runOnDbThread(db->{
db.delete("accounts", "`id`=?", new String[]{id});
db.delete("instances", "`domain` NOT IN (SELECT DISTINCT `domain` FROM `accounts`)", new String[]{});
});
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
NotificationManager nm=MastodonApp.context.getSystemService(NotificationManager.class);
nm.deleteNotificationChannelGroup(id);
@ -302,7 +294,12 @@ public class AccountSessionManager{
public void onSuccess(Account result){
session.self=result;
session.infoLastUpdated=System.currentTimeMillis();
writeAccountsFile();
runOnDbThread(db->{
ContentValues values=new ContentValues();
values.put("account_obj", MastodonAPIController.gson.toJson(result));
values.put("info_last_updated", session.infoLastUpdated);
db.update("accounts", values, "`id`=?", new String[]{session.getID()});
});
}
@Override
@ -320,7 +317,15 @@ public class AccountSessionManager{
public void onSuccess(List<LegacyFilter> result){
session.wordFilters=result;
session.filtersLastUpdated=System.currentTimeMillis();
writeAccountsFile();
runOnDbThread(db->{
ContentValues values=new ContentValues();
values.put("legacy_filters", new JsonObjectBuilder()
.add("filters", MastodonAPIController.gson.toJsonTree(session.wordFilters))
.add("updated", session.filtersLastUpdated)
.build()
.toString());
db.update("accounts", values, "`id`=?", new String[]{session.getID()});
});
}
@Override
@ -353,13 +358,10 @@ public class AccountSessionManager{
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Emoji> result){
InstanceInfoStorageWrapper emojis=new InstanceInfoStorageWrapper();
emojis.lastUpdated=System.currentTimeMillis();
emojis.emojis=result;
emojis.instance=instance;
customEmojis.put(domain, groupCustomEmojis(emojis));
instancesLastUpdated.put(domain, emojis.lastUpdated);
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(emojis, domain));
long lastUpdated=System.currentTimeMillis();
customEmojis.put(domain, groupCustomEmojis(result));
instancesLastUpdated.put(domain, lastUpdated);
runOnDbThread(db->insertInstanceIntoDatabase(db, domain, instance, result, lastUpdated));
E.post(new EmojiUpdatedEvent(domain));
}
@ -371,30 +373,17 @@ public class AccountSessionManager{
.execNoAuth(domain);
}
private File getInstanceInfoFile(String domain){
return new File(MastodonApp.context.getFilesDir(), "instance_"+domain.replace('.', '_')+".json");
}
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
try(FileOutputStream out=new FileOutputStream(getInstanceInfoFile(domain))){
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
MastodonAPIController.gson.toJson(emojis, writer);
writer.flush();
}catch(IOException x){
Log.w(TAG, "Error writing instance info file for "+domain, x);
}
}
private void readInstanceInfo(Set<String> domains){
for(String domain:domains){
try(FileInputStream in=new FileInputStream(getInstanceInfoFile(domain))){
InputStreamReader reader=new InputStreamReader(in, StandardCharsets.UTF_8);
InstanceInfoStorageWrapper emojis=MastodonAPIController.gson.fromJson(reader, InstanceInfoStorageWrapper.class);
private void readInstanceInfo(SQLiteDatabase db, Set<String> domains){
try(Cursor cursor=db.query("instances", null, "`domain` IN ("+String.join(", ", Collections.nCopies(domains.size(), "?"))+")", domains.toArray(new String[0]), null, null, null)){
ContentValues values=new ContentValues();
while(cursor.moveToNext()){
DatabaseUtils.cursorRowToContentValues(cursor, values);
String domain=values.getAsString("domain");
Instance instance=MastodonAPIController.gson.fromJson(values.getAsString("instance_obj"), Instance.class);
List<Emoji> emojis=MastodonAPIController.gson.fromJson(values.getAsString("emojis"), new TypeToken<List<Emoji>>(){}.getType());
instances.put(domain, instance);
customEmojis.put(domain, groupCustomEmojis(emojis));
instances.put(domain, emojis.instance);
instancesLastUpdated.put(domain, emojis.lastUpdated);
}catch(Exception x){
Log.w(TAG, "Error reading instance info file for "+domain, x);
instancesLastUpdated.put(domain, values.getAsLong("last_updated"));
}
}
if(!loadedInstances){
@ -403,8 +392,8 @@ public class AccountSessionManager{
}
}
private List<EmojiCategory> groupCustomEmojis(InstanceInfoStorageWrapper emojis){
return emojis.emojis.stream()
private List<EmojiCategory> groupCustomEmojis(List<Emoji> emojis){
return emojis.stream()
.filter(e->e.visibleInPicker)
.collect(Collectors.groupingBy(e->e.category==null ? "" : e.category))
.entrySet()
@ -427,7 +416,48 @@ public class AccountSessionManager{
AccountSession session=getAccount(id);
session.self=account;
session.infoLastUpdated=System.currentTimeMillis();
writeAccountsFile();
runOnDbThread(db->{
ContentValues values=new ContentValues();
values.put("account_obj", MastodonAPIController.gson.toJson(account));
values.put("info_last_updated", session.infoLastUpdated);
db.update("accounts", values, "`id`=?", new String[]{session.getID()});
});
}
public void updateAccountPreferences(String id, Preferences prefs){
AccountSession session=getAccount(id);
session.preferences=prefs;
runOnDbThread(db->{
ContentValues values=new ContentValues();
values.put("preferences", MastodonAPIController.gson.toJson(prefs));
db.update("accounts", values, "`id`=?", new String[]{session.getID()});
});
}
public void writeAccountPushSettings(String id){
AccountSession session=getAccount(id);
runWithDatabase(db->{ // Called from a background thread anyway
ContentValues values=new ContentValues();
values.put("push_keys", new JsonObjectBuilder()
.add("auth", session.pushAuthKey)
.add("private", session.pushPrivateKey)
.add("public", session.pushPublicKey)
.build()
.toString());
values.put("push_subscription", MastodonAPIController.gson.toJson(session.pushSubscription));
values.put("flags", session.getFlagsForDatabase());
db.update("accounts", values, "`id`=?", new String[]{id});
});
}
public void writeAccountActivationInfo(String id){
AccountSession session=getAccount(id);
runOnDbThread(db->{
ContentValues values=new ContentValues();
values.put("activation_info", MastodonAPIController.gson.toJson(session.activationInfo));
values.put("flags", session.getFlagsForDatabase());
db.update("accounts", values, "`id`=?", new String[]{id});
});
}
private void maybeUpdateShortcuts(){
@ -487,8 +517,24 @@ public class AccountSessionManager{
}
private void runOnDbThread(DatabaseRunnable r){
cancelDelayedClose();
CacheController.databaseThread.postRunnable(()->{
synchronized(databaseLock){
cancelDelayedClose();
try{
SQLiteDatabase db=getOrOpenDatabase();
r.run(db);
}catch(SQLiteException|IOException x){
Log.w(TAG, x);
}finally{
closeDelayed();
}
}
}, 0);
}
private void runWithDatabase(DatabaseRunnable r){
synchronized(databaseLock){
cancelDelayedClose();
try{
SQLiteDatabase db=getOrOpenDatabase();
r.run(db);
@ -497,7 +543,7 @@ public class AccountSessionManager{
}finally{
closeDelayed();
}
}, 0);
}
}
public void runIfDonationCampaignNotDismissed(String id, Runnable action){
@ -523,14 +569,13 @@ public class AccountSessionManager{
runOnDbThread(db->db.delete("dismissed_donation_campaigns", null, null));
}
private static class SessionsStorageWrapper{
public List<AccountSession> accounts;
}
private static class InstanceInfoStorageWrapper{
public Instance instance;
public List<Emoji> emojis;
public long lastUpdated;
private static void insertInstanceIntoDatabase(SQLiteDatabase db, String domain, Instance instance, List<Emoji> emojis, long lastUpdated){
ContentValues values=new ContentValues();
values.put("domain", domain);
values.put("instance_obj", MastodonAPIController.gson.toJson(instance));
values.put("emojis", MastodonAPIController.gson.toJson(emojis));
values.put("last_updated", lastUpdated);
db.insertWithOnConflict("instances", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
private static class DatabaseHelper extends SQLiteOpenHelper{
@ -545,11 +590,71 @@ public class AccountSessionManager{
`id` text PRIMARY KEY,
`dismissed_at` bigint
)""");
createAccountsAndInstancesTables(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
if(oldVersion<2){
createAccountsAndInstancesTables(db);
File accountsFile=new File(MastodonApp.context.getFilesDir(), "accounts.json");
if(accountsFile.exists()){
HashSet<String> domains=new HashSet<>();
try(FileInputStream in=new FileInputStream(accountsFile)){
JsonObject jobj=JsonParser.parseReader(new InputStreamReader(in, StandardCharsets.UTF_8)).getAsJsonObject();
ContentValues values=new ContentValues();
for(JsonElement jacc:jobj.getAsJsonArray("accounts")){
AccountSession session=MastodonAPIController.gson.fromJson(jacc, AccountSession.class);
domains.add(session.domain.toLowerCase());
session.toContentValues(values);
db.insertWithOnConflict("accounts", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
}catch(Exception x){
Log.e(TAG, "Error migrating accounts", x);
return;
}
accountsFile.delete();
for(String domain:domains){
File file=new File(MastodonApp.context.getFilesDir(), "instance_"+domain.replace('.', '_')+".json");
try(FileInputStream in=new FileInputStream(file)){
JsonObject jobj=JsonParser.parseReader(new InputStreamReader(in, StandardCharsets.UTF_8)).getAsJsonObject();
insertInstanceIntoDatabase(db, domain, MastodonAPIController.gson.fromJson(jobj.get("instance"), Instance.class),
MastodonAPIController.gson.fromJson(jobj.get("emojis"), new TypeToken<>(){}.getType()), jobj.get("last_updated").getAsLong());
}catch(Exception x){
Log.w(TAG, "Error reading instance info file for "+domain, x);
}
file.delete();
}
}
}
}
private void createAccountsAndInstancesTables(SQLiteDatabase db){
db.execSQL("""
CREATE TABLE `accounts` (
`id` text PRIMARY KEY,
`domain` text,
`account_obj` text,
`token` text,
`application` text,
`info_last_updated` bigint,
`flags` bigint,
`push_keys` text,
`push_subscription` text,
`legacy_filters` text DEFAULT NULL,
`push_id` text,
`activation_info` text,
`preferences` text
)""");
db.execSQL("""
CREATE TABLE `instances` (
`domain` text PRIMARY KEY,
`instance_obj` text,
`emojis` text,
`last_updated` bigint
)""");
}
}
}

View File

@ -149,7 +149,7 @@ public class AccountActivationFragment extends ToolbarFragment{
session.activationInfo.lastEmailConfirmationResend=System.currentTimeMillis();
}
lastResendTime=session.activationInfo.lastEmailConfirmationResend;
AccountSessionManager.getInstance().writeAccountsFile();
AccountSessionManager.getInstance().writeAccountActivationInfo(accountID);
updateResendTimer();
}