Local database
This commit is contained in:
parent
03c0b183cb
commit
04405b028c
|
@ -10,7 +10,7 @@ android {
|
|||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 31
|
||||
versionCode 8
|
||||
versionCode 9
|
||||
versionName "0.1"
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
package org.joinmastodon.android.api;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.utils.WorkerThread;
|
||||
|
||||
public class CacheController{
|
||||
private static final String TAG="CacheController";
|
||||
private static final int DB_VERSION=1;
|
||||
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
||||
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||
|
||||
private final String accountID;
|
||||
private DatabaseHelper db;
|
||||
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
||||
|
||||
static{
|
||||
databaseThread.start();
|
||||
}
|
||||
|
||||
public CacheController(String accountID){
|
||||
this.accountID=accountID;
|
||||
}
|
||||
|
||||
public void getHomeTimeline(String maxID, int count, Callback<List<Status>> callback){
|
||||
cancelDelayedClose();
|
||||
databaseThread.postRunnable(()->{
|
||||
try{
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
try(Cursor cursor=db.query("home_timeline", new String[]{"json"}, maxID==null ? null : "`id`>?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
|
||||
if(cursor.getCount()==count){
|
||||
ArrayList<Status> result=new ArrayList<>();
|
||||
cursor.moveToFirst();
|
||||
do{
|
||||
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
||||
status.postprocess();
|
||||
result.add(status);
|
||||
}while(cursor.moveToNext());
|
||||
uiHandler.post(()->callback.onSuccess(result));
|
||||
return;
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "getHomeTimeline: corrupted status object in database", x);
|
||||
}
|
||||
new GetHomeTimeline(maxID, null, count)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
callback.onSuccess(result);
|
||||
putHomeTimeline(result, maxID==null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
callback.onError(error);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage())));
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private void putHomeTimeline(List<Status> posts, boolean clear){
|
||||
cancelDelayedClose();
|
||||
databaseThread.postRunnable(()->{
|
||||
try{
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
if(clear)
|
||||
db.delete("home_timeline", null, null);
|
||||
ContentValues values=new ContentValues(2);
|
||||
for(Status s:posts){
|
||||
values.put("id", s.id);
|
||||
values.put("json", MastodonAPIController.gson.toJson(s));
|
||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public void getNotifications(String maxID, int count, boolean onlyMentions, Callback<List<Notification>> callback){
|
||||
cancelDelayedClose();
|
||||
databaseThread.postRunnable(()->{
|
||||
try{
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
try(Cursor cursor=db.query(onlyMentions ? "notifications_mentions" : "notifications_all", new String[]{"json"}, maxID==null ? null : "`id`>?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
|
||||
if(cursor.getCount()==count){
|
||||
ArrayList<Notification> result=new ArrayList<>();
|
||||
cursor.moveToFirst();
|
||||
do{
|
||||
Notification ntf=MastodonAPIController.gson.fromJson(cursor.getString(0), Notification.class);
|
||||
ntf.postprocess();
|
||||
result.add(ntf);
|
||||
}while(cursor.moveToNext());
|
||||
uiHandler.post(()->callback.onSuccess(result));
|
||||
return;
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "getNotifications: corrupted notification object in database", x);
|
||||
}
|
||||
new GetNotifications(maxID, count, onlyMentions ? EnumSet.complementOf(EnumSet.of(Notification.Type.MENTION)): null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
callback.onSuccess(result);
|
||||
putNotifications(result, onlyMentions, maxID==null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
callback.onError(error);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage())));
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private void putNotifications(List<Notification> notifications, boolean onlyMentions, boolean clear){
|
||||
cancelDelayedClose();
|
||||
databaseThread.postRunnable(()->{
|
||||
try{
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
String table=onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||
if(clear)
|
||||
db.delete(table, null, null);
|
||||
ContentValues values=new ContentValues(3);
|
||||
for(Notification n:notifications){
|
||||
values.put("id", n.id);
|
||||
values.put("json", MastodonAPIController.gson.toJson(n));
|
||||
values.put("type", n.type.ordinal());
|
||||
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private void closeDelayed(){
|
||||
databaseThread.postRunnable(databaseCloseRunnable, 10_000);
|
||||
}
|
||||
|
||||
public void closeDatabase(){
|
||||
if(db!=null){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.d(TAG, "closeDatabase");
|
||||
db.close();
|
||||
db=null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelDelayedClose(){
|
||||
if(db!=null){
|
||||
databaseThread.handler.removeCallbacks(databaseCloseRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
private SQLiteDatabase getOrOpenDatabase(){
|
||||
if(db==null)
|
||||
db=new DatabaseHelper();
|
||||
return db.getWritableDatabase();
|
||||
}
|
||||
|
||||
private class DatabaseHelper extends SQLiteOpenHelper{
|
||||
|
||||
public DatabaseHelper(){
|
||||
super(MastodonApp.context, accountID+".db", null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db){
|
||||
db.execSQL("""
|
||||
CREATE TABLE `home_timeline` (
|
||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||
`json` TEXT NOT NULL,
|
||||
`flags` INTEGER NOT NULL DEFAULT 0
|
||||
)""");
|
||||
db.execSQL("""
|
||||
CREATE TABLE `notifications_all` (
|
||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||
`json` TEXT NOT NULL,
|
||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||
`type` INTEGER NOT NULL
|
||||
)""");
|
||||
db.execSQL("""
|
||||
CREATE TABLE `notifications_mentions` (
|
||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||
`json` TEXT NOT NULL,
|
||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||
`type` INTEGER NOT NULL
|
||||
)""");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.joinmastodon.android.api.session;
|
||||
|
||||
import org.joinmastodon.android.api.CacheController;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.StatusInteractionController;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
@ -19,6 +20,7 @@ public class AccountSession{
|
|||
public boolean activated=true;
|
||||
private transient MastodonAPIController apiController;
|
||||
private transient StatusInteractionController statusInteractionController;
|
||||
private transient CacheController cacheController;
|
||||
|
||||
AccountSession(Token token, Account self, Application app, String domain, int tootCharLimit, Instance instance, boolean activated){
|
||||
this.token=token;
|
||||
|
@ -48,4 +50,10 @@ public class AccountSession{
|
|||
statusInteractionController=new StatusInteractionController(getID());
|
||||
return statusInteractionController;
|
||||
}
|
||||
|
||||
public CacheController getCacheController(){
|
||||
if(cacheController==null)
|
||||
cacheController=new CacheController(getID());
|
||||
return cacheController;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,6 +143,8 @@ public class AccountSessionManager{
|
|||
|
||||
public void removeAccount(String id){
|
||||
AccountSession session=getAccount(id);
|
||||
session.getCacheController().closeDatabase();
|
||||
MastodonApp.context.deleteDatabase(id+".db");
|
||||
sessions.remove(id);
|
||||
if(lastActiveAccountID.equals(id)){
|
||||
if(sessions.isEmpty())
|
||||
|
|
|
@ -58,14 +58,22 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetHomeTimeline(offset>0 ? getMaxID() : null, null, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
// currentRequest=new GetHomeTimeline(offset>0 ? getMaxID() : null, null, count)
|
||||
// .setCallback(new SimpleCallback<>(this){
|
||||
// @Override
|
||||
// public void onSuccess(List<Status> result){
|
||||
// onDataLoaded(result, !result.isEmpty());
|
||||
// }
|
||||
// })
|
||||
// .exec(accountID);
|
||||
AccountSessionManager.getInstance()
|
||||
.getAccount(accountID).getCacheController()
|
||||
.getHomeTimeline(offset>0 ? getMaxID() : null, count, new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.view.View;
|
|||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
@ -36,15 +37,13 @@ import me.grishka.appkit.api.SimpleCallback;
|
|||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||
private EnumSet<Notification.Type> types;
|
||||
private boolean onlyMentions;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.notifications);
|
||||
if(getArguments().getBoolean("onlyMentions", false)){
|
||||
types=EnumSet.complementOf(EnumSet.of(Notification.Type.MENTION));
|
||||
}
|
||||
onlyMentions=getArguments().getBoolean("onlyMentions", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,8 +78,24 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetNotifications(offset>0 ? getMaxID() : null, count, types)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
// new GetNotifications(offset>0 ? getMaxID() : null, count, types)
|
||||
// .setCallback(new SimpleCallback<>(this){
|
||||
// @Override
|
||||
// public void onSuccess(List<Notification> result){
|
||||
// if(refreshing)
|
||||
// relationships.clear();
|
||||
// onDataLoaded(result, !result.isEmpty());
|
||||
// Set<String> needRelationships=result.stream()
|
||||
// .filter(ntf->ntf.status==null && !relationships.containsKey(ntf.account.id))
|
||||
// .map(ntf->ntf.account.id)
|
||||
// .collect(Collectors.toSet());
|
||||
// loadRelationships(needRelationships);
|
||||
// }
|
||||
// })
|
||||
// .exec(accountID);
|
||||
AccountSessionManager.getInstance()
|
||||
.getAccount(accountID).getCacheController()
|
||||
.getNotifications(offset>0 ? getMaxID() : null, count, onlyMentions, new SimpleCallback<List<Notification>>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
if(refreshing)
|
||||
|
@ -92,8 +107,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
.collect(Collectors.toSet());
|
||||
loadRelationships(needRelationships);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue