diff --git a/app/src/main/java/app/fedilab/android/mastodon/client/entities/app/TimelineCacheLogs.java b/app/src/main/java/app/fedilab/android/mastodon/client/entities/app/TimelineCacheLogs.java new file mode 100644 index 000000000..851add4d1 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/mastodon/client/entities/app/TimelineCacheLogs.java @@ -0,0 +1,246 @@ +package app.fedilab.android.mastodon.client.entities.app; +/* Copyright 2023 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.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.mastodon.exception.DBException; +import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.sqlite.Sqlite; + +public class TimelineCacheLogs { + + 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("type") + public Timeline.TimeLineEnum type; + @SerializedName("created_at") + public Date created_at; + @SerializedName("fetched") + public int fetched; + @SerializedName("failed") + public int failed; + @SerializedName("inserted") + public int inserted; + @SerializedName("updated") + public int updated; + @SerializedName("frequency") + public int frequency; + private Context context; + + public TimelineCacheLogs() { + db = null; + } + + + public TimelineCacheLogs(Context context) { + //Creation of the DB with tables + this.context = context; + this.db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + } + + + /** + * get all cache timelineCacheLogs for home + * + * @param baseAccount Status {@link BaseAccount} + * @return List + * @throws DBException Exception + */ + public List getHome(BaseAccount baseAccount) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + String selection = Sqlite.COL_INSTANCE + "='" + baseAccount.instance + "' AND " + Sqlite.COL_USER_ID + "= '" + baseAccount.user_id + "' AND " + Sqlite.COL_SLUG + "= '" + Timeline.TimeLineEnum.HOME.getValue() + "' "; + try { + Cursor c = db.query(Sqlite.TABLE_TIMELINE_CACHE_LOGS, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + " ASC", null); + return cursorToListOfStatuses(c); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + /** + * Insert a status in db + * + * @param timelineCacheLogs {@link TimelineCacheLogs} + * @return long - db id + * @throws DBException exception with database + */ + public long insert(TimelineCacheLogs timelineCacheLogs) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + ContentValues values = new ContentValues(); + values.put(Sqlite.COL_USER_ID, timelineCacheLogs.user_id); + values.put(Sqlite.COL_INSTANCE, timelineCacheLogs.instance); + values.put(Sqlite.COL_SLUG, timelineCacheLogs.slug); + values.put(Sqlite.COL_TYPE, timelineCacheLogs.type.getValue()); + values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date())); + values.put(Sqlite.COL_FAILED, timelineCacheLogs.failed); + values.put(Sqlite.COL_FETCHED, timelineCacheLogs.fetched); + values.put(Sqlite.COL_FREQUENCY, timelineCacheLogs.frequency); + values.put(Sqlite.COL_INSERTED, timelineCacheLogs.inserted); + values.put(Sqlite.COL_UPDATED, timelineCacheLogs.updated); + //Inserts token + try { + return db.insertOrThrow(Sqlite.TABLE_TIMELINE_CACHE_LOGS, null, values); + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + + /** + * delete all cache for all account + * + * @return long - db id + * @throws DBException exception with database + */ + public long deleteForAllAccount() throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + try { + return db.delete(Sqlite.TABLE_TIMELINE_CACHE_LOGS, null, null); + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + /** + * delete all cache for all account after 7 days + * + * @return long - db id + * @throws DBException exception with database + */ + public long deleteForAllAccountAfter7Days() throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DATE, -7); + Date date = cal.getTime(); + String dateStr = Helper.dateToString(date); + try { + return db.delete(Sqlite.TABLE_TIMELINE_CACHE_LOGS, Sqlite.COL_CREATED_AT + " < ?", new String[]{dateStr}); + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + /** + * delete all cache for an slug + * + * @return long - db id + * @throws DBException exception with database + */ + public long deleteForSlug(String slug) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + try { + return db.delete(Sqlite.TABLE_TIMELINE_CACHE_LOGS, + Sqlite.COL_SLUG + " = ? AND " + Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =?", + new String[]{slug, MainActivity.currentUserID, MainActivity.currentInstance}); + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + + public int count(BaseAccount account) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + Cursor mCount = db.rawQuery("select count(*) from " + Sqlite.TABLE_TIMELINE_CACHE_LOGS + + " where " + Sqlite.COL_INSTANCE + " = '" + account.instance + "' AND " + Sqlite.COL_USER_ID + " = '" + account.user_id + "'", null); + mCount.moveToFirst(); + int count = mCount.getInt(0); + mCount.close(); + return count; + } + + /** + * Convert a cursor to list of TimelineCacheLogs + * + * @param c Cursor + * @return List + */ + private List cursorToListOfStatuses(Cursor c) { + //No element found + if (c.getCount() == 0) { + c.close(); + return null; + } + List timelineCacheLogsList = new ArrayList<>(); + while (c.moveToNext()) { + TimelineCacheLogs timelineCacheLogs = convertCursorToTimelineCacheLogs(c); + timelineCacheLogsList.add(timelineCacheLogs); + } + //Close the cursor + c.close(); + return timelineCacheLogsList; + } + + /** + * Read cursor and hydrate without closing it + * + * @param c - Cursor + * @return Timeline + */ + private TimelineCacheLogs convertCursorToTimelineCacheLogs(Cursor c) { + TimelineCacheLogs timelineCacheLogs = new TimelineCacheLogs(); + timelineCacheLogs.id = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_ID)); + timelineCacheLogs.type = Timeline.TimeLineEnum.valueOf(c.getString(c.getColumnIndexOrThrow(Sqlite.COL_TYPE))); + timelineCacheLogs.instance = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_INSTANCE)); + timelineCacheLogs.user_id = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_USER_ID)); + timelineCacheLogs.slug = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_SLUG)); + timelineCacheLogs.created_at = Helper.stringToDate(context, c.getString(c.getColumnIndexOrThrow(Sqlite.COL_CREATED_AT))); + timelineCacheLogs.failed = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_FAILED)); + timelineCacheLogs.fetched = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_FETCHED)); + timelineCacheLogs.inserted = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_INSERTED)); + timelineCacheLogs.updated = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_UPDATED)); + timelineCacheLogs.frequency = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_FREQUENCY)); + return timelineCacheLogs; + } + + +} diff --git a/app/src/main/java/app/fedilab/android/mastodon/jobs/FetchHomeWorker.java b/app/src/main/java/app/fedilab/android/mastodon/jobs/FetchHomeWorker.java index 422f1e99e..f240c26fd 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/jobs/FetchHomeWorker.java +++ b/app/src/main/java/app/fedilab/android/mastodon/jobs/FetchHomeWorker.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.mastodon.client.endpoints.MastodonTimelinesService; import app.fedilab.android.mastodon.client.entities.api.Pagination; import app.fedilab.android.mastodon.client.entities.api.Status; @@ -48,6 +49,7 @@ import app.fedilab.android.mastodon.client.entities.app.Account; import app.fedilab.android.mastodon.client.entities.app.BaseAccount; import app.fedilab.android.mastodon.client.entities.app.StatusCache; import app.fedilab.android.mastodon.client.entities.app.Timeline; +import app.fedilab.android.mastodon.client.entities.app.TimelineCacheLogs; import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.MastodonHelper; @@ -161,16 +163,21 @@ public class FetchHomeWorker extends Worker { .getDefaultSharedPreferences(context); boolean fetch_home = prefs.getBoolean(context.getString(R.string.SET_FETCH_HOME) + account.user_id + account.instance, false); + boolean failed = false; + int fetched = 0, inserted = 0, updated = 0, frequency = 0; + String timeRefresh = prefs.getString(context.getString(R.string.SET_FETCH_HOME_DELAY_VALUE) + MainActivity.currentUserID + MainActivity.currentInstance, "60"); + try { + frequency = Integer.parseInt(timeRefresh); + } catch (Exception ignored) { + } if (fetch_home) { - int max_calls = 5; - int status_per_page = 80; + int max_calls = 10; + int status_per_page = 40; //Browse last 400 home messages boolean canContinue = true; int call = 0; String max_id = null; MastodonTimelinesService mastodonTimelinesService = init(account.instance); - int insertValue = 0; - StatusCache lastStatusCache = null; while (canContinue && call < max_calls) { Call> homeCall = mastodonTimelinesService.getHome(account.token, max_id, null, null, status_per_page, null); if (homeCall != null) { @@ -178,6 +185,7 @@ public class FetchHomeWorker extends Worker { if (homeResponse.isSuccessful()) { List statusList = homeResponse.body(); if (statusList != null && statusList.size() > 0) { + fetched += statusList.size(); for (Status status : statusList) { StatusCache statusCacheDAO = new StatusCache(getApplicationContext()); StatusCache statusCache = new StatusCache(); @@ -186,9 +194,13 @@ public class FetchHomeWorker extends Worker { statusCache.status = status; statusCache.type = Timeline.TimeLineEnum.HOME; statusCache.status_id = status.id; - lastStatusCache = statusCache; try { - insertValue = statusCacheDAO.insertOrUpdate(statusCache, Timeline.TimeLineEnum.HOME.getValue()); + int insertOrUpdate = statusCacheDAO.insertOrUpdate(statusCache, Timeline.TimeLineEnum.HOME.getValue()); + if (insertOrUpdate == 1) { + inserted++; + } else { + updated++; + } } catch (DBException e) { e.printStackTrace(); } @@ -205,27 +217,36 @@ public class FetchHomeWorker extends Worker { } } else { canContinue = false; + failed = true; } + } else { + canContinue = false; + failed = true; } //Pause between calls (1 second) try { - Thread.sleep(1000); + Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } call++; } - //insertValue is for last status and equals zero if updated or 1 if inserted - if (lastStatusCache != null && insertValue == 1) { //Last inserted message was not in cache. - StatusCache statusCacheDAO = new StatusCache(getApplicationContext()); - lastStatusCache.status.isFetchMore = true; - lastStatusCache.status.positionFetchMore = Status.PositionFetchMore.TOP; - try { - statusCacheDAO.updateIfExists(lastStatusCache); - } catch (DBException e) { - throw new RuntimeException(e); - } + TimelineCacheLogs timelineCacheLogs = new TimelineCacheLogs(); + timelineCacheLogs.frequency = frequency; + timelineCacheLogs.fetched = fetched; + timelineCacheLogs.failed = failed ? 1 : 0; + timelineCacheLogs.updated = updated; + timelineCacheLogs.inserted = inserted; + timelineCacheLogs.slug = Timeline.TimeLineEnum.HOME.getValue(); + timelineCacheLogs.type = Timeline.TimeLineEnum.HOME; + timelineCacheLogs.user_id = account.user_id; + timelineCacheLogs.instance = account.instance; + try { + new TimelineCacheLogs(context).insert(timelineCacheLogs); + } catch (DBException e) { + throw new RuntimeException(e); } + } } 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 7dfa40545..6dd6b4c3a 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 = 10; + public static final int DB_VERSION = 11; public static final String DB_NAME = "fedilab_db"; //Table of owned accounts @@ -99,11 +99,13 @@ public class Sqlite extends SQLiteOpenHelper { public static final String COL_UPDATED = "UPDATED"; public static final String COL_FAILED = "FAILED"; public static final String COL_FREQUENCY = "FREQUENCY"; - public static final String COL_FETCHED_COUNT = "FETCHED_COUNT"; + public static final String COL_FETCHED = "FETCHED"; public static final String TABLE_CACHE_TAGS = "CACHE_TAGS"; public static final String COL_TAG = "TAG"; + public static final String TABLE_TIMELINE_CACHE_LOGS = "TIMELINE_CACHE_LOGS"; + private static final String CREATE_TABLE_USER_ACCOUNT = "CREATE TABLE " + TABLE_USER_ACCOUNT + " (" + COL_USER_ID + " TEXT NOT NULL, " @@ -219,6 +221,21 @@ public class Sqlite extends SQLiteOpenHelper { + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COL_TAG + " TEXT NOT NULL)"; + private final String CREATE_TABLE_TIMELINE_CACHE_LOGS = "CREATE TABLE " + + TABLE_TIMELINE_CACHE_LOGS + "(" + + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COL_INSTANCE + " TEXT NOT NULL, " + + COL_USER_ID + " TEXT NOT NULL, " + + COL_FETCHED + " INTEGER NOT NULL DEFAULT 0, " + + COL_INSERTED + " INTEGER NOT NULL DEFAULT 0, " + + COL_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + + COL_FAILED + " INTEGER NOT NULL DEFAULT 0, " + + COL_FREQUENCY + " INTEGER NOT NULL DEFAULT 0, " + + COL_SLUG + " TEXT NOT NULL, " + + COL_TYPE + " TEXT NOT NULL, " + + COL_CREATED_AT + " TEXT NOT NULL)"; + + public static SQLiteDatabase db; private static Sqlite sInstance; @@ -251,6 +268,7 @@ public class Sqlite extends SQLiteOpenHelper { db.execSQL(CREATE_TABLE_MUTED); db.execSQL(CREATE_TABLE_STORED_INSTANCES); db.execSQL(CREATE_TABLE_CACHE_TAGS); + db.execSQL(CREATE_TABLE_TIMELINE_CACHE_LOGS); } @Override @@ -281,6 +299,8 @@ public class Sqlite extends SQLiteOpenHelper { db.execSQL(CREATE_TABLE_STORED_INSTANCES); case 9: db.execSQL(CREATE_TABLE_CACHE_TAGS); + case 10: + db.execSQL(CREATE_TABLE_TIMELINE_CACHE_LOGS); default: break; }