diff --git a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java index 75f2c4ecb5..0fcf21db99 100644 --- a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java +++ b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java @@ -182,6 +182,32 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback ids = null; List matches = null; MailService iservice = null; diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index 6db2ff9901..9602d3ac29 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -2240,6 +2240,12 @@ class Core { Log.i(folder.name + " inline downloaded message id=" + message.id + " size=" + message.size + "/" + (body == null ? null : body.length())); + boolean fts = prefs.getBoolean("fts", false); + if (fts) { + FtsDbHelper ftsDb = new FtsDbHelper(context); + ftsDb.insert(message, HtmlHelper.getText(body)); + } + Long size = parts.getBodySize(); if (TextUtils.isEmpty(body) && size != null && size > 0) reportEmptyMessage(context, account, istore); @@ -2567,6 +2573,12 @@ class Core { Log.i(folder.name + " downloaded message id=" + message.id + " size=" + message.size + "/" + (body == null ? null : body.length())); + boolean fts = prefs.getBoolean("fts", false); + if (fts) { + FtsDbHelper ftsDb = new FtsDbHelper(context); + ftsDb.insert(message, HtmlHelper.getText(body)); + } + Long size = parts.getBodySize(); if (TextUtils.isEmpty(body) && size != null && size > 0) reportEmptyMessage(context, account, istore); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java index 8cb201b52a..644881dd23 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java @@ -47,6 +47,7 @@ import androidx.preference.PreferenceManager; public class FragmentOptionsMisc extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener { private SwitchCompat swExternalSearch; + private SwitchCompat swFts; private SwitchCompat swEnglish; private SwitchCompat swWatchdog; private SwitchCompat swUpdates; @@ -67,7 +68,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc private Group grpDebug; private final static String[] RESET_OPTIONS = new String[]{ - "english", "watchdog", "updates", "experiments", "crash_reports", "debug" + "fts", "english", "watchdog", "updates", "experiments", "crash_reports", "debug" }; private final static String[] RESET_QUESTIONS = new String[]{ @@ -88,6 +89,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc // Get controls swExternalSearch = view.findViewById(R.id.swExternalSearch); + swFts = view.findViewById(R.id.swFts); swEnglish = view.findViewById(R.id.swEnglish); swWatchdog = view.findViewById(R.id.swWatchdog); swUpdates = view.findViewById(R.id.swUpdates); @@ -126,6 +128,13 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc } }); + swFts.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("fts", checked).apply(); + } + }); + swEnglish.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -298,6 +307,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc int state = pm.getComponentEnabledSetting(new ComponentName(getContext(), ActivitySearch.class)); swExternalSearch.setChecked(state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED); + swFts.setChecked(prefs.getBoolean("fts", false)); swEnglish.setChecked(prefs.getBoolean("english", false)); swWatchdog.setChecked(prefs.getBoolean("watchdog", true)); swUpdates.setChecked(prefs.getBoolean("updates", true)); diff --git a/app/src/main/java/eu/faircode/email/FtsDbHelper.java b/app/src/main/java/eu/faircode/email/FtsDbHelper.java new file mode 100644 index 0000000000..9d1d479520 --- /dev/null +++ b/app/src/main/java/eu/faircode/email/FtsDbHelper.java @@ -0,0 +1,103 @@ +package eu.faircode.email; + +/* + This file is part of FairEmail. + + FairEmail 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. + + FairEmail 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 FairEmail. If not, see . + + Copyright 2018-2020 by Marcel Bokhorst (M66B) +*/ + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.mail.Address; + +import io.requery.android.database.sqlite.SQLiteDatabase; +import io.requery.android.database.sqlite.SQLiteOpenHelper; + +public class FtsDbHelper extends SQLiteOpenHelper { + // https://www.sqlite.org/fts3.html + private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "fts.db"; + + public FtsDbHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.i("FTS create"); + db.execSQL("CREATE VIRTUAL TABLE `message` USING fts4(`time`, `address`, `subject`, `keyword`, `text`)"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // Do nothing + } + + void insert(EntityMessage message, String text) { + Log.i("FTS insert id=" + message.id + " subject=" + message.subject + " text=" + text); + List
address = new ArrayList<>(); + if (message.from != null) + address.addAll(Arrays.asList(message.from)); + if (message.to != null) + address.addAll(Arrays.asList(message.to)); + if (message.cc != null) + address.addAll(Arrays.asList(message.cc)); + + try (SQLiteDatabase db = getWritableDatabase()) { + try { + db.beginTransaction(); + + db.delete("message", "docid = ?", new Object[]{message.id}); + + ContentValues cv = new ContentValues(); + cv.put("docid", message.id); + cv.put("time", message.received); + cv.put("address", MessageHelper.formatAddresses(address.toArray(new Address[0]), true, false)); + cv.put("subject", message.subject == null ? "" : message.subject); + cv.put("keyword", TextUtils.join(", ", message.keywords)); + cv.put("text", text); + db.insert("message", SQLiteDatabase.CONFLICT_FAIL, cv); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } + + List match(String search) { + Log.i("FTS search=" + search); + List result = new ArrayList<>(); + try (SQLiteDatabase db = getReadableDatabase()) { + try (Cursor cursor = db.query( + "message", new String[]{"docid"}, + "message MATCH ?", new Object[]{search}, + null, null, "time DESC", null)) { + while (cursor != null && cursor.moveToNext()) + result.add(cursor.getLong(0)); + } + } + Log.i("FTS result=" + result.size()); + return result; + } +} diff --git a/app/src/main/res/layout/fragment_options_misc.xml b/app/src/main/res/layout/fragment_options_misc.xml index cbf2a36c52..a0c0513db5 100644 --- a/app/src/main/res/layout/fragment_options_misc.xml +++ b/app/src/main/res/layout/fragment_options_misc.xml @@ -24,6 +24,29 @@ app:layout_constraintTop_toTopOf="parent" app:switchPadding="12dp" /> + + + + Max AES key size: %1$d Allow other apps to search in messages + Full text search Force English language Periodically check if FairEmail is still active Check for updates @@ -403,6 +404,7 @@ Some providers don\'t support this properly, which may cause synchronizing none or all messages This will transfer extra data and consume extra battery power, especially if a lot of messages are stored on the device Disabling this will reduce data and battery usage somewhat, but will disable updating the list of folders too + Enabling this will delete all local folders without subscription This will slow down synchronizing messages In addition to contacts provided by Android. Contact data will be stored for newly sent or received messages only when enabled. @@ -443,7 +445,7 @@ This Android version does not support notification grouping This Android version does not support notification channels - Enabling this will delete all local folders without subscription + Enabling this will increase search performance, but also increase battery usage and uses more storage space This will restart the app List of current experimental features Enable extra logging and show debug information at various places