1
0
Fork 0
mirror of https://github.com/M66B/FairEmail.git synced 2025-01-01 12:44:42 +00:00

Rule execution

This commit is contained in:
M66B 2019-01-17 21:41:00 +00:00
parent 699d1be7ec
commit 97c4d39a0f
10 changed files with 174 additions and 85 deletions

View file

@ -45,18 +45,20 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
private LifecycleOwner owner;
private LayoutInflater inflater;
private List<EntityRule> all = new ArrayList<>();
private List<EntityRule> filtered = new ArrayList<>();
private List<TupleRuleEx> all = new ArrayList<>();
private List<TupleRuleEx> filtered = new ArrayList<>();
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private View itemView;
private TextView tvName;
private TextView tvFolder;
ViewHolder(View itemView) {
super(itemView);
this.itemView = itemView.findViewById(R.id.clItem);
tvName = itemView.findViewById(R.id.tvName);
tvFolder = itemView.findViewById(R.id.tvFolder);
}
private void wire() {
@ -67,9 +69,10 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
itemView.setOnClickListener(null);
}
private void bindTo(EntityRule rule) {
private void bindTo(TupleRuleEx rule) {
itemView.setActivated(!rule.enabled);
tvName.setText(rule.name);
tvFolder.setText(rule.folderName + "/" + rule.accountName);
}
@Override
@ -78,7 +81,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
if (pos == RecyclerView.NO_POSITION)
return;
EntityRule rule = filtered.get(pos);
TupleRuleEx rule = filtered.get(pos);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
@ -94,15 +97,24 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
setHasStableIds(true);
}
public void set(@NonNull List<EntityRule> rules) {
public void set(@NonNull List<TupleRuleEx> rules) {
Log.i("Set rules=" + rules.size());
final Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
Collections.sort(rules, new Comparator<EntityRule>() {
Collections.sort(rules, new Comparator<TupleRuleEx>() {
@Override
public int compare(EntityRule r1, EntityRule r2) {
public int compare(TupleRuleEx r1, TupleRuleEx r2) {
int a = collator.compare(r1.accountName, r2.accountName);
if (a != 0)
return a;
int f = collator.compare(r1.folderName, r2.folderName);
if (f != 0)
return f;
int o = ((Integer) r1.order).compareTo(r2.order);
if (o != 0)
return 0;
return collator.compare(r1.name, r2.name);
}
});
@ -139,10 +151,10 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
}
private class MessageDiffCallback extends DiffUtil.Callback {
private List<EntityRule> prev;
private List<EntityRule> next;
private List<TupleRuleEx> prev;
private List<TupleRuleEx> next;
MessageDiffCallback(List<EntityRule> prev, List<EntityRule> next) {
MessageDiffCallback(List<TupleRuleEx> prev, List<TupleRuleEx> next) {
this.prev = prev;
this.next = next;
}
@ -159,15 +171,15 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
EntityRule r1 = prev.get(oldItemPosition);
EntityRule r2 = next.get(newItemPosition);
TupleRuleEx r1 = prev.get(oldItemPosition);
TupleRuleEx r2 = next.get(newItemPosition);
return r1.id.equals(r2.id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
EntityRule r1 = prev.get(oldItemPosition);
EntityRule r2 = next.get(newItemPosition);
TupleRuleEx r1 = prev.get(oldItemPosition);
TupleRuleEx r2 = next.get(newItemPosition);
return r1.equals(r2);
}
}
@ -191,7 +203,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
EntityRule rule = filtered.get(position);
TupleRuleEx rule = filtered.get(position);
holder.bindTo(rule);
holder.wire();
}

View file

@ -31,16 +31,20 @@ import androidx.room.Update;
public interface DaoRule {
@Query("SELECT * FROM rule" +
" WHERE folder = :folder" +
" AND enabled" +
" ORDER BY `order`")
List<EntityRule> getRules(long folder);
List<EntityRule> getEnabledRules(long folder);
@Query("SELECT rule.*, folder.account FROM rule" +
@Query("SELECT rule.*, folder.account, folder.name AS folderName, account.name AS accountName FROM rule" +
" JOIN folder ON folder.id = rule.folder" +
" JOIN account ON account.id = folder.account" +
" WHERE rule.id = :id")
TupleRuleEx getRule(long id);
@Query("SELECT * FROM rule ORDER BY `order`")
LiveData<List<EntityRule>> liveRules();
@Query("SELECT rule.*, folder.account, folder.name AS folderName, account.name AS accountName FROM rule" +
" JOIN folder ON folder.id = rule.folder" +
" JOIN account ON account.id = folder.account")
LiveData<List<TupleRuleEx>> liveRules();
@Insert
long insertRule(EntityRule rule);

View file

@ -67,28 +67,32 @@ public class EntityRule {
static final int TYPE_UNSEEN = 2;
static final int TYPE_MOVE = 3;
boolean matches(Context context, EntityMessage message) throws JSONException, IOException {
JSONObject jcondition = new JSONObject(condition);
String sender = jcondition.getString("sender");
String subject = jcondition.getString("subject");
String text = jcondition.getString("text");
boolean regex = jcondition.getBoolean("regex");
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);
String text = jcondition.optString("text", null);
boolean regex = jcondition.optBoolean("regex", false);
if (sender != null && message.from != null) {
if (matches(sender, MessageHelper.getFormattedAddresses(message.from, true), regex))
return true;
}
if (sender != null && message.from != null) {
if (matches(sender, MessageHelper.getFormattedAddresses(message.from, true), regex))
return true;
}
if (subject != null && message.subject != null) {
if (matches(subject, message.subject, regex))
return true;
}
if (subject != null && message.subject != null) {
if (matches(subject, message.subject, regex))
return true;
}
if (text != null && message.content) {
String body = message.read(context);
String santized = HtmlHelper.sanitize(body, true);
if (matches(text, santized, regex))
return true;
if (text != null && message.content) {
String body = message.read(context);
String santized = HtmlHelper.sanitize(body, true);
if (matches(text, santized, regex))
return true;
}
} catch (JSONException ex) {
Log.e(ex);
}
return false;
@ -102,30 +106,35 @@ public class EntityRule {
return haystack.contains(needle);
}
void execute(Context context, DB db, EntityMessage message) throws JSONException {
JSONObject jargs = new JSONObject(action);
switch (jargs.getInt("type")) {
case TYPE_SEEN:
onActionSeen(context, db, message, true);
break;
case TYPE_UNSEEN:
onActionSeen(context, db, message, false);
break;
case TYPE_MOVE:
onActionMove(context, db, message, jargs);
break;
void execute(Context context, DB db, EntityMessage message) {
try {
JSONObject jargs = new JSONObject(action);
int type = jargs.getInt("type");
Log.i("Executing rule=" + type + " message=" + message.id);
switch (type) {
case TYPE_SEEN:
onActionSeen(context, db, message, true);
break;
case TYPE_UNSEEN:
onActionSeen(context, db, message, false);
break;
case TYPE_MOVE:
onActionMove(context, db, message, jargs);
break;
}
} catch (JSONException ex) {
Log.e(ex);
}
}
private void onActionSeen(Context context, DB db, EntityMessage message, boolean seen) {
EntityOperation.queue(context, db, message, EntityOperation.SEEN, seen);
message.seen = seen;
}
private void onActionMove(Context context, DB db, EntityMessage message, JSONObject jargs) throws JSONException {
long target = jargs.getLong("target");
boolean seen = jargs.getBoolean("seen");
if (seen)
EntityOperation.queue(context, db, message, EntityOperation.SEEN, true);
EntityOperation.queue(context, db, message, EntityOperation.MOVE, target);
}

View file

@ -58,8 +58,7 @@ public class FragmentRule extends FragmentBase {
private EditText etSubject;
private EditText etText;
private Spinner spAction;
private Spinner spMove;
private CheckBox cbMoveSeen;
private Spinner spTarget;
private BottomNavigationView bottom_navigation;
private ContentLoadingProgressBar pbWait;
private Group grpReady;
@ -68,6 +67,7 @@ public class FragmentRule extends FragmentBase {
private ArrayAdapter<EntityAccount> adapterAccount;
private ArrayAdapter<EntityFolder> adapterFolder;
private ArrayAdapter<Action> adapterAction;
private ArrayAdapter<EntityFolder> adapterTarget;
private long id = -1;
@ -95,8 +95,7 @@ public class FragmentRule extends FragmentBase {
etSubject = view.findViewById(R.id.etSubject);
etText = view.findViewById(R.id.etText);
spAction = view.findViewById(R.id.spAction);
spMove = view.findViewById(R.id.spMove);
cbMoveSeen = view.findViewById(R.id.cbMoveSeen);
spTarget = view.findViewById(R.id.spTarget);
bottom_navigation = view.findViewById(R.id.bottom_navigation);
pbWait = view.findViewById(R.id.pbWait);
grpReady = view.findViewById(R.id.grpReady);
@ -114,10 +113,14 @@ public class FragmentRule extends FragmentBase {
adapterAction.setDropDownViewResource(R.layout.spinner_item1_dropdown);
spAction.setAdapter(adapterAction);
adapterTarget = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList<EntityFolder>());
adapterTarget.setDropDownViewResource(R.layout.spinner_item1_dropdown);
spTarget.setAdapter(adapterTarget);
List<Action> actions = new ArrayList<>();
actions.add(new Action(EntityRule.TYPE_SEEN, getString(R.string.title_seen)));
actions.add(new Action(EntityRule.TYPE_UNSEEN, getString(R.string.title_unseen)));
//actions.add(new Action(EntityRule.TYPE_MOVE, getString(R.string.title_move)));
actions.add(new Action(EntityRule.TYPE_MOVE, getString(R.string.title_move)));
adapterAction.addAll(actions);
spAccount.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@ -133,6 +136,23 @@ public class FragmentRule extends FragmentBase {
}
});
spAction.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
Action action = (Action) adapterView.getAdapter().getItem(position);
onActionSelected(action.type);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
onActionSelected(-1);
}
private void onActionSelected(int type) {
grpMove.setVisibility(type == EntityRule.TYPE_MOVE ? View.VISIBLE : View.GONE);
}
});
bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
@ -223,6 +243,9 @@ public class FragmentRule extends FragmentBase {
spAccount.setTag(rule.account);
spFolder.setTag(rule.folder);
if (type == EntityRule.TYPE_MOVE)
spTarget.setTag(jaction.getLong("target"));
for (int pos = 0; pos < adapterAccount.getCount(); pos++)
if (adapterAccount.getItem(pos).id.equals(rule.account)) {
spAccount.setSelection(pos);
@ -265,6 +288,9 @@ public class FragmentRule extends FragmentBase {
adapterFolder.clear();
adapterFolder.addAll(folders);
adapterTarget.clear();
adapterTarget.addAll(folders);
long account = args.getLong("account");
if (account == (Long) spAccount.getTag()) {
Long folder = (Long) spFolder.getTag();
@ -273,8 +299,17 @@ public class FragmentRule extends FragmentBase {
spFolder.setSelection(pos);
break;
}
} else
Long target = (Long) spTarget.getTag();
for (int pos = 0; pos < folders.size(); pos++)
if (folders.get(pos).id.equals(target)) {
spTarget.setSelection(pos);
break;
}
} else {
spFolder.setSelection(0);
spTarget.setSelection(0);
}
grpReady.setVisibility(View.VISIBLE);
bottom_navigation.setVisibility(View.VISIBLE);
@ -352,8 +387,13 @@ public class FragmentRule extends FragmentBase {
Action action = (Action) spAction.getSelectedItem();
JSONObject jaction = new JSONObject();
if (action != null)
if (action != null) {
jaction.put("type", action.type);
if (action.type == EntityRule.TYPE_MOVE) {
EntityFolder target = (EntityFolder) spTarget.getSelectedItem();
jaction.put("target", target.id);
}
}
Bundle args = new Bundle();
args.putLong("id", id);

View file

@ -88,9 +88,9 @@ public class FragmentRules extends FragmentBase {
super.onActivityCreated(savedInstanceState);
DB db = DB.getInstance(getContext());
db.rule().liveRules().observe(getViewLifecycleOwner(), new Observer<List<EntityRule>>() {
db.rule().liveRules().observe(getViewLifecycleOwner(), new Observer<List<TupleRuleEx>>() {
@Override
public void onChanged(List<EntityRule> rules) {
public void onChanged(List<TupleRuleEx> rules) {
if (rules == null)
rules = new ArrayList<>();

View file

@ -1037,7 +1037,9 @@ public class ServiceSynchronize extends LifecycleService {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) imessage,
message.id, db.folder().getFolderDownload(folder.id));
message.id,
db.folder().getFolderDownload(folder.id),
db.rule().getEnabledRules(folder.id));
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@ -1124,7 +1126,9 @@ public class ServiceSynchronize extends LifecycleService {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) e.getMessage(),
message.id, db.folder().getFolderDownload(folder.id));
message.id,
db.folder().getFolderDownload(folder.id),
db.rule().getEnabledRules(folder.id));
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@ -2300,6 +2304,8 @@ public class ServiceSynchronize extends LifecycleService {
db.folder().setFolderSyncState(folder.id, "downloading");
List<EntityRule> rules = db.rule().getEnabledRules(folder.id);
//fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
// Download messages/attachments
@ -2317,7 +2323,7 @@ public class ServiceSynchronize extends LifecycleService {
downloadMessage(
this,
folder, ifolder, (IMAPMessage) isub[j],
ids[from + j], download);
ids[from + j], download, rules);
db.setTransactionSuccessful();
} catch (FolderClosedException ex) {
throw ex;
@ -2575,7 +2581,7 @@ public class ServiceSynchronize extends LifecycleService {
static void downloadMessage(
Context context,
EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage,
long id, boolean download) throws MessagingException, IOException {
long id, boolean download, List<EntityRule> rules) throws MessagingException, IOException {
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message == null)
@ -2584,7 +2590,7 @@ public class ServiceSynchronize extends LifecycleService {
if (message.setContactInfo(context))
db.message().updateMessage(message);
if (download) {
if (download || rules.size() > 0) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
long maxSize = prefs.getInt("download", 32768);
if (maxSize == 0)
@ -2624,7 +2630,7 @@ public class ServiceSynchronize extends LifecycleService {
MessageHelper.MessageParts parts = helper.getMessageParts();
if (!message.content)
if (!message.content) {
if (!metered || (message.size != null && message.size < maxSize)) {
String body = parts.getHtml(context);
message.write(context, body);
@ -2633,6 +2639,11 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(folder.name + " downloaded message id=" + message.id + " size=" + message.size);
}
for (EntityRule rule : rules)
if (rule.matches(context, message))
rule.execute(context, db, message);
}
for (int i = 0; i < attachments.size(); i++) {
EntityAttachment attachment = attachments.get(i);
if (!attachment.available)

View file

@ -21,13 +21,17 @@ package eu.faircode.email;
public class TupleRuleEx extends EntityRule {
public long account;
public String folderName;
public String accountName;
@Override
public boolean equals(Object obj) {
if (obj instanceof TupleRuleEx) {
TupleRuleEx other = (TupleRuleEx) obj;
return (super.equals(obj) &&
this.account == other.account);
this.account == other.account &&
(this.folderName == null ? other.folderName == null : this.folderName.equals(other.folderName)) &&
(this.accountName == null ? other.accountName == null : this.accountName.equals(other.accountName)));
} else
return false;
}

View file

@ -26,6 +26,7 @@ import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.imap.IMAPStore;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
@ -230,7 +231,8 @@ public class ViewModelBrowse extends ViewModel {
message = ServiceSynchronize.synchronizeMessage(state.context,
folder, state.ifolder, (IMAPMessage) isub[j], true);
ServiceSynchronize.downloadMessage(state.context,
folder, state.ifolder, (IMAPMessage) isub[j], message.id, false);
folder, state.ifolder, (IMAPMessage) isub[j], message.id,
false, new ArrayList<EntityRule>());
count++;
}
db.message().setMessageFound(message.account, message.thread);

View file

@ -198,7 +198,7 @@
app:layout_constraintTop_toBottomOf="@id/tvAction" />
<TextView
android:id="@+id/tvMove"
android:id="@+id/tvTarget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
@ -208,33 +208,24 @@
app:layout_constraintTop_toBottomOf="@id/spAction" />
<Spinner
android:id="@+id/spMove"
android:id="@+id/spTarget"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvMove" />
<CheckBox
android:id="@+id/cbMoveSeen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_rule_seen"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/spMove" />
app:layout_constraintTop_toBottomOf="@id/tvTarget" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpReady"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="tvName,etName,tvAccount,spAccount,tvFolder,spFolder,tvFolderRemark,tvOrder,etOrder,spFolder,tvSender,etSender,tvSubject,etSubject,tvText,etText,tvAction,spAction" />
app:constraint_referenced_ids="tvName,etName,tvOrder,etOrder,cbEnabled,tvAccount,spAccount,tvFolder,spFolder,tvFolderRemark,tvSender,etSender,tvSubject,etSubject,tvText,etText,tvAction,spAction,tvTarget,spTarget" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpMove"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="tvMove,spMove,cbMoveSeen" />
app:constraint_referenced_ids="tvTarget,spTarget" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -25,6 +25,22 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvFolder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:ellipsize="end"
android:gravity="end"
android:maxLines="1"
android:text="Name"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvName" />
<View
android:id="@+id/vSeparator"
android:layout_width="match_parent"
@ -33,6 +49,6 @@
android:layout_marginBottom="6dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvName" />
app:layout_constraintTop_toBottomOf="@id/tvFolder" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>