mirror of https://github.com/M66B/FairEmail.git
Rule improvements
This commit is contained in:
parent
c995179237
commit
b1ebc3ffd2
File diff suppressed because it is too large
Load Diff
|
@ -49,7 +49,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
|
|||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 37,
|
||||
version = 38,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
|
@ -453,6 +453,13 @@ public abstract class DB extends RoomDatabase {
|
|||
db.execSQL("CREATE INDEX `index_rule_order` ON `rule` (`order`)");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(37, 38) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i("DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `rule` ADD COLUMN `stop` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,9 @@ import org.json.JSONObject;
|
|||
import java.io.IOException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.ForeignKey;
|
||||
|
@ -59,6 +62,8 @@ public class EntityRule {
|
|||
@NonNull
|
||||
public boolean enabled;
|
||||
@NonNull
|
||||
public boolean stop;
|
||||
@NonNull
|
||||
public String condition;
|
||||
@NonNull
|
||||
public String action;
|
||||
|
@ -70,27 +75,54 @@ public class EntityRule {
|
|||
boolean matches(Context context, EntityMessage message) throws IOException {
|
||||
try {
|
||||
JSONObject jcondition = new JSONObject(condition);
|
||||
String sender = jcondition.optString("sender", null);
|
||||
String subject = jcondition.optString("subject", null);
|
||||
boolean regex = jcondition.optBoolean("regex", false);
|
||||
|
||||
if (sender != null && message.from != null) {
|
||||
if (matches(sender, MessageHelper.getFormattedAddresses(message.from, true), regex))
|
||||
return true;
|
||||
JSONObject jsender = jcondition.optJSONObject("sender");
|
||||
if (jsender != null) {
|
||||
String sender = jsender.getString("value");
|
||||
boolean regex = jsender.getBoolean("regex");
|
||||
|
||||
boolean matches = false;
|
||||
if (message.from != null) {
|
||||
for (Address from : message.from) {
|
||||
InternetAddress ia = (InternetAddress) from;
|
||||
String personal = ia.getPersonal();
|
||||
String formatted = ((personal == null ? "" : personal + " ") + "<" + ia.getAddress() + ">");
|
||||
if (matches(sender, formatted, regex)) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!matches)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subject != null && message.subject != null) {
|
||||
if (matches(subject, message.subject, regex))
|
||||
return true;
|
||||
JSONObject jsubject = jcondition.optJSONObject("subject");
|
||||
if (jsubject != null) {
|
||||
String subject = jsubject.getString("value");
|
||||
boolean regex = jsubject.getBoolean("regex");
|
||||
|
||||
if (!matches(subject, message.subject, regex))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safeguard
|
||||
if (jsender == null && jsubject == null)
|
||||
return false;
|
||||
} catch (JSONException ex) {
|
||||
Log.e(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean matches(String needle, String haystack, boolean regex) {
|
||||
Log.i("Matches needle=" + needle + " haystack=" + haystack + " regex=" + regex);
|
||||
|
||||
if (needle == null || haystack == null)
|
||||
return false;
|
||||
|
||||
if (regex) {
|
||||
Pattern pattern = Pattern.compile(needle);
|
||||
return pattern.matcher(haystack).matches();
|
||||
|
@ -138,6 +170,7 @@ public class EntityRule {
|
|||
this.name.equals(other.name) &&
|
||||
this.order == other.order &&
|
||||
this.enabled == other.enabled &&
|
||||
this.stop == other.stop &&
|
||||
this.condition.equals(other.condition) &&
|
||||
this.action.equals(other.action);
|
||||
} else
|
||||
|
|
|
@ -52,8 +52,11 @@ public class FragmentRule extends FragmentBase {
|
|||
private EditText etName;
|
||||
private EditText etOrder;
|
||||
private CheckBox cbEnabled;
|
||||
private CheckBox cbStop;
|
||||
private EditText etSender;
|
||||
private CheckBox cbSender;
|
||||
private EditText etSubject;
|
||||
private CheckBox cbSubject;
|
||||
private Spinner spAction;
|
||||
private Spinner spTarget;
|
||||
private BottomNavigationView bottom_navigation;
|
||||
|
@ -88,8 +91,11 @@ public class FragmentRule extends FragmentBase {
|
|||
etName = view.findViewById(R.id.etName);
|
||||
etOrder = view.findViewById(R.id.etOrder);
|
||||
cbEnabled = view.findViewById(R.id.cbEnabled);
|
||||
cbStop = view.findViewById(R.id.cbStop);
|
||||
etSender = view.findViewById(R.id.etSender);
|
||||
cbSender = view.findViewById(R.id.cbSender);
|
||||
etSubject = view.findViewById(R.id.etSubject);
|
||||
cbSubject = view.findViewById(R.id.cbSubject);
|
||||
spAction = view.findViewById(R.id.spAction);
|
||||
spTarget = view.findViewById(R.id.spTarget);
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
|
@ -199,11 +205,17 @@ public class FragmentRule extends FragmentBase {
|
|||
JSONObject jcondition = (rule == null ? new JSONObject() : new JSONObject(rule.condition));
|
||||
JSONObject jaction = (rule == null ? new JSONObject() : new JSONObject(rule.action));
|
||||
|
||||
JSONObject jsender = jcondition.optJSONObject("sender");
|
||||
JSONObject jsubject = jcondition.optJSONObject("subject");
|
||||
|
||||
etName.setText(rule == null ? null : rule.name);
|
||||
etOrder.setText(rule == null ? null : Integer.toString(rule.order));
|
||||
cbEnabled.setChecked(rule == null ? true : rule.enabled);
|
||||
etSender.setText(jcondition.optString("sender"));
|
||||
etSubject.setText(jcondition.optString("subject"));
|
||||
cbEnabled.setChecked(rule == null || rule.enabled);
|
||||
cbStop.setChecked(rule != null && rule.stop);
|
||||
etSender.setText(jsender == null ? null : jsender.optString("value"));
|
||||
cbSender.setChecked(jsender != null && jsender.optBoolean("regex", false));
|
||||
etSubject.setText(jsubject == null ? null : jsubject.optString("value"));
|
||||
cbSubject.setChecked(jsubject != null && jsubject.optBoolean("regex", false));
|
||||
|
||||
int type = jaction.optInt("type", -1);
|
||||
for (int pos = 0; pos < adapterAction.getCount(); pos++)
|
||||
|
@ -298,18 +310,27 @@ public class FragmentRule extends FragmentBase {
|
|||
try {
|
||||
Helper.setViewsEnabled(view, false);
|
||||
|
||||
String sender = etSender.getText().toString();
|
||||
String subject = etSubject.getText().toString();
|
||||
|
||||
JSONObject jcondition = new JSONObject();
|
||||
if (!TextUtils.isEmpty(sender))
|
||||
jcondition.put("sender", sender);
|
||||
if (!TextUtils.isEmpty(subject))
|
||||
jcondition.put("subject", subject);
|
||||
|
||||
Action action = (Action) spAction.getSelectedItem();
|
||||
String sender = etSender.getText().toString();
|
||||
if (!TextUtils.isEmpty(sender)) {
|
||||
JSONObject jsender = new JSONObject();
|
||||
jsender.put("value", sender);
|
||||
jsender.put("regex", cbSender.isChecked());
|
||||
jcondition.put("sender", jsender);
|
||||
}
|
||||
|
||||
String subject = etSubject.getText().toString();
|
||||
if (!TextUtils.isEmpty(subject)) {
|
||||
JSONObject jsubject = new JSONObject();
|
||||
jsubject.put("value", subject);
|
||||
jsubject.put("regex", cbSubject.isChecked());
|
||||
jcondition.put("subject", jsubject);
|
||||
}
|
||||
|
||||
JSONObject jaction = new JSONObject();
|
||||
|
||||
Action action = (Action) spAction.getSelectedItem();
|
||||
if (action != null) {
|
||||
jaction.put("type", action.type);
|
||||
if (action.type == EntityRule.TYPE_MOVE) {
|
||||
|
@ -324,6 +345,7 @@ public class FragmentRule extends FragmentBase {
|
|||
args.putString("name", etName.getText().toString());
|
||||
args.putString("order", etOrder.getText().toString());
|
||||
args.putBoolean("enabled", cbEnabled.isChecked());
|
||||
args.putBoolean("stop", cbStop.isChecked());
|
||||
args.putString("condition", jcondition.toString());
|
||||
args.putString("action", jaction.toString());
|
||||
|
||||
|
@ -339,17 +361,26 @@ public class FragmentRule extends FragmentBase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) {
|
||||
protected Void onExecute(Context context, Bundle args) throws JSONException {
|
||||
long id = args.getLong("id");
|
||||
long folder = args.getLong("folder");
|
||||
String name = args.getString("name");
|
||||
String order = args.getString("order");
|
||||
boolean enabled = args.getBoolean("enabled");
|
||||
boolean stop = args.getBoolean("stop");
|
||||
String condition = args.getString("condition");
|
||||
String action = args.getString("action");
|
||||
|
||||
if (TextUtils.isEmpty(name))
|
||||
throw new IllegalArgumentException(getString(R.string.title_rule_name_missing));
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_rule_name_missing));
|
||||
|
||||
JSONObject jcondition = new JSONObject(condition);
|
||||
JSONObject jsender = jcondition.optJSONObject("sender");
|
||||
JSONObject jsubject = jcondition.optJSONObject("subject");
|
||||
|
||||
if (TextUtils.isEmpty(jsender.optString("value")) &&
|
||||
TextUtils.isEmpty(jsubject.optString("value")))
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_rule_condition_missing));
|
||||
|
||||
if (TextUtils.isEmpty(order))
|
||||
order = "1";
|
||||
|
@ -361,6 +392,7 @@ public class FragmentRule extends FragmentBase {
|
|||
rule.name = name;
|
||||
rule.order = Integer.parseInt(order);
|
||||
rule.enabled = enabled;
|
||||
rule.stop = stop;
|
||||
rule.condition = condition;
|
||||
rule.action = action;
|
||||
rule.id = db.rule().insertRule(rule);
|
||||
|
@ -370,6 +402,7 @@ public class FragmentRule extends FragmentBase {
|
|||
rule.name = name;
|
||||
rule.order = Integer.parseInt(order);
|
||||
rule.enabled = enabled;
|
||||
rule.stop = stop;
|
||||
rule.condition = condition;
|
||||
rule.action = action;
|
||||
db.rule().updateRule(rule);
|
||||
|
|
|
@ -2589,8 +2589,11 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
|
||||
if (filter)
|
||||
for (EntityRule rule : rules)
|
||||
if (rule.matches(context, message))
|
||||
if (rule.matches(context, message)) {
|
||||
rule.execute(context, db, message);
|
||||
if (rule.stop)
|
||||
break;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
|
|
@ -67,17 +67,38 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etOrder" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbStop"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_rule_stop"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbEnabled" />
|
||||
|
||||
<!-- condition -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/title_rule_sender"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/cbSender"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cbSender"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbEnabled" />
|
||||
app:layout_constraintTop_toTopOf="@+id/cbSender" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/title_rule_regex"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbStop" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSender"
|
||||
|
@ -87,37 +108,60 @@
|
|||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvSender" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSender" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSubject"
|
||||
android:id="@+id/tvAndSender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_rule_and"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/etSender" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSubject"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/title_rule_subject"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/cbSubject"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cbSubject"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etSender" />
|
||||
app:layout_constraintTop_toTopOf="@+id/cbSubject" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSubject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_rule_regex"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAndSender" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSubject"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textCapSentences"
|
||||
android:inputType="textEmailAddress"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvSubject" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSubject" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/title_rule_action"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etSubject" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/etSubject" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spAction"
|
||||
|
@ -132,7 +176,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_rule_folder"
|
||||
android:text="@string/title_rule_target"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spAction" />
|
||||
|
@ -149,7 +193,7 @@
|
|||
android:id="@+id/grpReady"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="tvName,etName,tvOrder,etOrder,cbEnabled,tvAccount,spAccount,tvFolder,spFolder,tvFolderRemark,tvSender,etSender,tvSubject,etSubject,tvText,etText,tvAction,spAction,tvTarget,spTarget" />
|
||||
app:constraint_referenced_ids="tvName,etName,tvOrder,etOrder,cbEnabled,cbStop,tvSender,etSender,cbSender,tvAndSender,tvSubject,etSubject,cbSubject,tvAction,spAction,tvTarget,spTarget" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpMove"
|
||||
|
|
|
@ -384,13 +384,17 @@
|
|||
<string name="title_rule_name">Name</string>
|
||||
<string name="title_rule_order">Order</string>
|
||||
<string name="title_rule_enabled">Enabled</string>
|
||||
<string name="title_rule_sender">Sender</string>
|
||||
<string name="title_rule_subject">Subject</string>
|
||||
<string name="title_rule_stop">Stop processing</string>
|
||||
<string name="title_rule_sender">Sender contains</string>
|
||||
<string name="title_rule_subject">Subject contains</string>
|
||||
<string name="title_rule_header">Header contains</string>
|
||||
<string name="title_rule_regex">Regex</string>
|
||||
<string name="title_rule_and">AND</string>
|
||||
<string name="title_rule_action">Action</string>
|
||||
<string name="title_rule_target">Target</string>
|
||||
<string name="title_rule_target">To</string>
|
||||
<string name="title_rule_seen">Mark as read</string>
|
||||
<string name="title_rule_folder">Folder</string>
|
||||
<string name="title_rule_name_missing">Rule name missing</string>
|
||||
<string name="title_rule_condition_missing">Condition missing</string>
|
||||
|
||||
<string name="title_action_seen">Mark read</string>
|
||||
<string name="title_action_archive">Archive</string>
|
||||
|
|
Loading…
Reference in New Issue