From e4375381e9cc6a12d5d3ce1c7f9943cf96fa242e Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 7 May 2022 11:14:32 +0200 Subject: [PATCH] Store timelines in db for faster start / switch between them. --- .../android/client/entities/QuickLoad.java | 257 ++++++++++++++++++ .../client/entities/app/SavedValues.java | 76 ------ .../app/fedilab/android/sqlite/Sqlite.java | 17 +- .../timeline/FragmentMastodonTimeline.java | 27 +- 4 files changed, 286 insertions(+), 91 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/client/entities/QuickLoad.java delete mode 100644 app/src/main/java/app/fedilab/android/client/entities/app/SavedValues.java diff --git a/app/src/main/java/app/fedilab/android/client/entities/QuickLoad.java b/app/src/main/java/app/fedilab/android/client/entities/QuickLoad.java new file mode 100644 index 000000000..d2c6ae54b --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/QuickLoad.java @@ -0,0 +1,257 @@ +package app.fedilab.android.client.entities; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.client.mastodon.entities.Status; +import app.fedilab.android.exception.DBException; +import app.fedilab.android.helper.SpannableHelper; +import app.fedilab.android.sqlite.Sqlite; + +public class QuickLoad { + + private final SQLiteDatabase db; + @SerializedName("id") + public long id; + @SerializedName("user_id") + public String user_id; + @SerializedName("instance") + public String instance; + @SerializedName("slug") + public String slug; + @SerializedName("position") + public int position; + @SerializedName("statuses") + public List statuses; + private Context _mContext; + + public QuickLoad() { + db = null; + } + + public QuickLoad(Context context) { + //Creation of the DB with tables + this.db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + _mContext = context; + } + + /** + * Check if the current timeline can be stored + * + * @param timeLineType - Timeline.TimeLineEnum + * @return boolean + */ + private static boolean cannotBeStored(Timeline.TimeLineEnum timeLineType) { + return timeLineType != Timeline.TimeLineEnum.HOME && timeLineType != Timeline.TimeLineEnum.LOCAL && timeLineType != Timeline.TimeLineEnum.PUBLIC && timeLineType != Timeline.TimeLineEnum.REMOTE && timeLineType != Timeline.TimeLineEnum.LIST && timeLineType != Timeline.TimeLineEnum.TAG; + } + + /** + * Insert or update a QuickLoad + * + * @param quickLoad {@link QuickLoad} + * @throws DBException exception with database + */ + private void insertOrUpdate(QuickLoad quickLoad) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + boolean exists = quickLoadExist(quickLoad); + if (exists) { + updateStatus(quickLoad); + } else { + insertQuickLoad(quickLoad); + } + } + + /** + * Check if a QuickLoad exists in db + * + * @param quickLoad QuickLoad {@link QuickLoad} + * @return boolean - QuickLoad exists + * @throws DBException Exception + */ + public boolean quickLoadExist(QuickLoad quickLoad) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + Cursor mCount = db.rawQuery("select count(*) from " + Sqlite.TABLE_QUICK_LOAD + + " where " + Sqlite.COL_SLUG + " = '" + quickLoad.slug + "'" + + " AND " + Sqlite.COL_INSTANCE + " = '" + quickLoad.instance + "'" + + " AND " + Sqlite.COL_USER_ID + "= '" + quickLoad.user_id + "'", null); + mCount.moveToFirst(); + int count = mCount.getInt(0); + mCount.close(); + return (count > 0); + } + + /** + * @param position - current position in timeline + * @param timeLineType - Timeline.TimeLineEnum + * @param statusList - List to save + * @param ident - the name for pinned timeline + */ + public void storeTimeline(int position, Timeline.TimeLineEnum timeLineType, List statusList, String ident) { + if (cannotBeStored(timeLineType)) { + return; + } + String key = timeLineType.getValue(); + if (ident != null) { + key += "|" + ident; + } + QuickLoad quickLoad = new QuickLoad(); + quickLoad.position = position; + quickLoad.statuses = statusList; + quickLoad.slug = key; + quickLoad.instance = MainActivity.currentInstance; + quickLoad.user_id = MainActivity.currentUserID; + try { + insertOrUpdate(quickLoad); + } catch (DBException e) { + e.printStackTrace(); + } + } + + /** + * Insert a QuickLoad in db + * + * @param quickLoad {@link QuickLoad} + * @throws DBException exception with database + */ + private void insertQuickLoad(QuickLoad quickLoad) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + ContentValues values = new ContentValues(); + values.put(Sqlite.COL_USER_ID, quickLoad.user_id); + values.put(Sqlite.COL_INSTANCE, quickLoad.instance); + values.put(Sqlite.COL_SLUG, quickLoad.slug); + values.put(Sqlite.COL_POSITION, quickLoad.position); + values.put(Sqlite.COL_STATUSES, StatusDraft.mastodonStatusListToStringStorage(quickLoad.statuses)); + //Inserts token + try { + db.insertOrThrow(Sqlite.TABLE_QUICK_LOAD, null, values); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Retrieves saved values + * + * @param timeLineType - Timeline.TimeLineEnum + * @param ident - the name for pinned timeline + * @return SavedValues + */ + public QuickLoad getSavedValue(Timeline.TimeLineEnum timeLineType, String ident) { + if (cannotBeStored(timeLineType)) { + return null; + } + String key = timeLineType.getValue(); + if (ident != null) { + key += "|" + ident; + } + try { + return get(key); + } catch (DBException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Update a QuickLoad in db + * + * @param quickLoad {@link QuickLoad} + * @throws DBException exception with database + */ + private void updateStatus(QuickLoad quickLoad) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + ContentValues values = new ContentValues(); + values.put(Sqlite.COL_POSITION, quickLoad.position); + values.put(Sqlite.COL_STATUSES, StatusDraft.mastodonStatusListToStringStorage(quickLoad.statuses)); + //Inserts token + try { + db.update(Sqlite.TABLE_QUICK_LOAD, + values, Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =? AND " + Sqlite.COL_SLUG + "=?", + new String[]{quickLoad.user_id, quickLoad.instance, quickLoad.slug}); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Get paginated statuses from db + * + * @return Statuses + * @throws DBException - throws a db exception + */ + private QuickLoad get(String slug) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + try { + Cursor c = db.query(Sqlite.TABLE_QUICK_LOAD, null, Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =? AND " + Sqlite.COL_SLUG + "=?", + new String[]{MainActivity.currentUserID, MainActivity.currentInstance, slug}, null, null, null, "1"); + return cursorToQuickLoad(c); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + /** + * Convert a cursor to QuickLoad + * + * @param c Cursor + * @return QuickLoad + */ + private QuickLoad cursorToQuickLoad(Cursor c) { + //No element found + if (c.getCount() == 0) { + c.close(); + return null; + } + //Take the first element + c.moveToFirst(); + QuickLoad quickLoad = new QuickLoad(); + quickLoad.id = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_ID)); + quickLoad.instance = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_INSTANCE)); + quickLoad.user_id = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_USER_ID)); + quickLoad.slug = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_SLUG)); + quickLoad.statuses = StatusDraft.restoreStatusListFromString(c.getString(c.getColumnIndexOrThrow(Sqlite.COL_STATUSES))); + quickLoad.position = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_POSITION)); + + //TimelineHelper.filterStatus(_mContext, quickLoad.statuses, TimelineHelper.FilterTimeLineType.PUBLIC); + quickLoad.statuses = SpannableHelper.convertStatus(_mContext, quickLoad.statuses); + //Close the cursor + c.close(); + return quickLoad; + } + + +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/SavedValues.java b/app/src/main/java/app/fedilab/android/client/entities/app/SavedValues.java deleted file mode 100644 index cb6bc3f3a..000000000 --- a/app/src/main/java/app/fedilab/android/client/entities/app/SavedValues.java +++ /dev/null @@ -1,76 +0,0 @@ -package app.fedilab.android.client.entities.app; -/* Copyright 2022 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import java.util.HashMap; -import java.util.List; - -import app.fedilab.android.client.entities.Timeline; -import app.fedilab.android.client.mastodon.entities.Status; - -public class SavedValues { - - public static HashMap storedStates = new HashMap<>(); - public int position; - public List statusList; - - /** - * Retrieves saved values - * - * @param timeLineType - Timeline.TimeLineEnum - * @param ident - the name for pinned timeline - * @return SavedValues - */ - public static SavedValues getSavedValue(Timeline.TimeLineEnum timeLineType, String ident) { - if (!canBestored(timeLineType)) { - return null; - } - String key = timeLineType.getValue(); - if (ident != null) { - key += "|" + ident; - } - return storedStates.get(key); - } - - /** - * Check if the current timeline can be stored - * - * @param timeLineType - Timeline.TimeLineEnum - * @return boolean - */ - private static boolean canBestored(Timeline.TimeLineEnum timeLineType) { - return timeLineType == Timeline.TimeLineEnum.HOME || timeLineType == Timeline.TimeLineEnum.LOCAL || timeLineType == Timeline.TimeLineEnum.PUBLIC || timeLineType == Timeline.TimeLineEnum.REMOTE || timeLineType == Timeline.TimeLineEnum.LIST || timeLineType == Timeline.TimeLineEnum.TAG; - } - - /** - * @param position - current position in timeline - * @param timeLineType - Timeline.TimeLineEnum - * @param statusList - List to save - * @param ident - the name for pinned timeline - */ - public static void storeTimeline(int position, Timeline.TimeLineEnum timeLineType, List statusList, String ident) { - if (!canBestored(timeLineType)) { - return; - } - String key = timeLineType.getValue(); - if (ident != null) { - key += "|" + ident; - } - SavedValues savedValues = new SavedValues(); - savedValues.position = position; - savedValues.statusList = statusList; - storedStates.put(key, savedValues); - } -} diff --git a/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java b/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java index ccfe6bb9a..aef5e8048 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java +++ b/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java @@ -23,7 +23,7 @@ import android.database.sqlite.SQLiteOpenHelper; public class Sqlite extends SQLiteOpenHelper { - public static final int DB_VERSION = 1; + public static final int DB_VERSION = 2; public static final String DB_NAME = "fedilab_db"; //Table of owned accounts @@ -73,6 +73,10 @@ public class Sqlite extends SQLiteOpenHelper { public static final String COL_SCHEDULED_AT = "SCHEDULED_AT"; public static final String COL_REBLOGGED = "REBLOGGED"; public static final String COL_WORKER_UUID = "WORKER_UUID"; + //Quick load + public static final String TABLE_QUICK_LOAD = "QUICK_LOAD"; + public static final String COL_SLUG = "SLUG"; + public static final String COL_STATUSES = "STATUSES"; private static final String CREATE_TABLE_USER_ACCOUNT = "CREATE TABLE " + TABLE_USER_ACCOUNT + " (" + COL_USER_ID + " TEXT NOT NULL, " @@ -145,6 +149,14 @@ public class Sqlite extends SQLiteOpenHelper { + COL_WORKER_UUID + " TEXT, " + COL_SCHEDULED_AT + " TEXT NOT NULL)"; + private static final String CREATE_TABLE_QUICK_LOAD = "CREATE TABLE IF NOT EXISTS " + TABLE_QUICK_LOAD + " (" + + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COL_INSTANCE + " TEXT NOT NULL, " + + COL_USER_ID + " TEXT NOT NULL, " + + COL_SLUG + " TEXT NOT NULL, " + + COL_POSITION + " INTEGER, " + + COL_STATUSES + " TEXT NOT NULL)"; + public static SQLiteDatabase db; private static Sqlite sInstance; @@ -171,6 +183,7 @@ public class Sqlite extends SQLiteOpenHelper { db.execSQL(CREATE_TABLE_STATUS_DRAFT); db.execSQL(CREATE_TABLE_PINNED_TIMELINES); db.execSQL(CREATE_TABLE_SCHEDULE_BOOST); + db.execSQL(CREATE_TABLE_QUICK_LOAD); } @Override @@ -182,6 +195,8 @@ public class Sqlite extends SQLiteOpenHelper { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //noinspection SwitchStatementWithTooFewBranches switch (oldVersion) { + case 1: + db.execSQL(CREATE_TABLE_QUICK_LOAD); default: break; } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java index a3049799d..429ad6ccc 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -41,8 +41,8 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; +import app.fedilab.android.client.entities.QuickLoad; import app.fedilab.android.client.entities.Timeline; -import app.fedilab.android.client.entities.app.SavedValues; import app.fedilab.android.client.entities.app.TagTimeline; import app.fedilab.android.client.mastodon.entities.Account; import app.fedilab.android.client.mastodon.entities.Marker; @@ -351,11 +351,14 @@ public class FragmentMastodonTimeline extends Fragment { @Override public void onDestroyView() { - super.onDestroyView(); - if (mLayoutManager != null) { int position = mLayoutManager.findFirstVisibleItemPosition(); - new Thread(() -> SavedValues.storeTimeline(position, timelineType, statuses, ident)).start(); + new Thread(() -> { + try { + new QuickLoad(requireActivity()).storeTimeline(position, timelineType, statuses, ident); + } catch (Exception ignored) { + } + }).start(); } //Update last read id for home timeline storeMarker(); @@ -365,6 +368,7 @@ public class FragmentMastodonTimeline extends Fragment { LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(receive_action); statusAdapter = null; binding = null; + super.onDestroyView(); } private void storeMarker() { @@ -403,18 +407,13 @@ public class FragmentMastodonTimeline extends Fragment { private void route(DIRECTION direction) { new Thread(() -> { - SavedValues savedValues = SavedValues.getSavedValue(timelineType, ident); - if (savedValues != null && savedValues.statusList != null && savedValues.statusList.size() > 0) { + QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(timelineType, ident); + if (quickLoad != null && quickLoad.statuses != null && quickLoad.statuses.size() > 0) { Statuses statuses = new Statuses(); - statuses.statuses = savedValues.statusList; + statuses.statuses = quickLoad.statuses; statuses.pagination = new Pagination(); - statuses.pagination.max_id = savedValues.statusList.get(savedValues.statusList.size() - 1).id; - statuses.pagination.min_id = savedValues.statusList.get(0).id; - try { - Thread.sleep(200); - } catch (InterruptedException e) { - e.printStackTrace(); - } + statuses.pagination.max_id = quickLoad.statuses.get(quickLoad.statuses.size() - 1).id; + statuses.pagination.min_id = quickLoad.statuses.get(0).id; Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> initializeStatusesCommonView(statuses); mainHandler.post(myRunnable);