Added option to run rules daily

pull/210/head
M66B 3 years ago
parent 5b73a1a4e8
commit 6cdd2a36aa

File diff suppressed because it is too large Load Diff

@ -76,6 +76,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private View view; private View view;
private ImageView ivDaily;
private TextView tvName; private TextView tvName;
private TextView tvOrder; private TextView tvOrder;
private ImageView ivStop; private ImageView ivStop;
@ -90,6 +91,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
super(itemView); super(itemView);
view = itemView.findViewById(R.id.clItem); view = itemView.findViewById(R.id.clItem);
ivDaily = itemView.findViewById(R.id.ivDaily);
tvName = itemView.findViewById(R.id.tvName); tvName = itemView.findViewById(R.id.tvName);
tvOrder = itemView.findViewById(R.id.tvOrder); tvOrder = itemView.findViewById(R.id.tvOrder);
ivStop = itemView.findViewById(R.id.ivStop); ivStop = itemView.findViewById(R.id.ivStop);
@ -111,6 +113,7 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> {
private void bindTo(TupleRuleEx rule) { private void bindTo(TupleRuleEx rule) {
view.setActivated(!rule.enabled); view.setActivated(!rule.enabled);
ivDaily.setVisibility(rule.daily ? View.VISIBLE : View.GONE);
tvName.setText(rule.name); tvName.setText(rule.name);
tvOrder.setText(Integer.toString(rule.order)); tvOrder.setText(Integer.toString(rule.order));
ivStop.setVisibility(rule.stop ? View.VISIBLE : View.INVISIBLE); ivStop.setVisibility(rule.stop ? View.VISIBLE : View.INVISIBLE);

@ -255,6 +255,7 @@ public class ApplicationEx extends Application
WorkerAutoUpdate.init(this); WorkerAutoUpdate.init(this);
WorkerCleanup.init(this); WorkerCleanup.init(this);
WorkerDailyRules.init(this);
} }
registerReceiver(onScreenOff, new IntentFilter(Intent.ACTION_SCREEN_OFF)); registerReceiver(onScreenOff, new IntentFilter(Intent.ACTION_SCREEN_OFF));

@ -5004,7 +5004,8 @@ class Core {
try { try {
boolean executed = false; boolean executed = false;
for (EntityRule rule : rules) for (EntityRule rule : rules)
if (rule.matches(context, message, headers, html)) { if (!rule.daily &&
rule.matches(context, message, headers, html)) {
rule.execute(context, message); rule.execute(context, message);
executed = true; executed = true;
if (rule.stop) if (rule.stop)

@ -67,7 +67,7 @@ import javax.mail.internet.InternetAddress;
// https://developer.android.com/topic/libraries/architecture/room.html // https://developer.android.com/topic/libraries/architecture/room.html
@Database( @Database(
version = 259, version = 260,
entities = { entities = {
EntityIdentity.class, EntityIdentity.class,
EntityAccount.class, EntityAccount.class,
@ -2616,6 +2616,12 @@ public abstract class DB extends RoomDatabase {
db.execSQL("UPDATE account SET keep_alive_noop = 0" + db.execSQL("UPDATE account SET keep_alive_noop = 0" +
" WHERE host = 'outlook.office365.com' AND pop = " + EntityAccount.TYPE_IMAP); " WHERE host = 'outlook.office365.com' AND pop = " + EntityAccount.TYPE_IMAP);
} }
}).addMigrations(new Migration(259, 260) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase db) {
logMigration(startVersion, endVersion);
db.execSQL("ALTER TABLE `rule` ADD COLUMN `daily` INTEGER NOT NULL DEFAULT 0");
}
}).addMigrations(new Migration(998, 999) { }).addMigrations(new Migration(998, 999) {
@Override @Override
public void migrate(@NonNull SupportSQLiteDatabase db) { public void migrate(@NonNull SupportSQLiteDatabase db) {

@ -93,6 +93,8 @@ public class EntityRule {
@NonNull @NonNull
public boolean enabled; public boolean enabled;
@NonNull @NonNull
public boolean daily;
@NonNull
public boolean stop; public boolean stop;
@NonNull @NonNull
public String condition; public String condition;
@ -1258,6 +1260,7 @@ public class EntityRule {
this.name.equals(other.name) && this.name.equals(other.name) &&
this.order == other.order && this.order == other.order &&
this.enabled == other.enabled && this.enabled == other.enabled &&
this.daily == other.daily &&
this.stop == other.stop && this.stop == other.stop &&
this.condition.equals(other.condition) && this.condition.equals(other.condition) &&
this.action.equals(other.action) && this.action.equals(other.action) &&
@ -1309,6 +1312,7 @@ public class EntityRule {
json.put("name", name); json.put("name", name);
json.put("order", order); json.put("order", order);
json.put("enabled", enabled); json.put("enabled", enabled);
json.put("daily", daily);
json.put("stop", stop); json.put("stop", stop);
json.put("condition", condition); json.put("condition", condition);
json.put("action", action); json.put("action", action);
@ -1325,6 +1329,7 @@ public class EntityRule {
rule.name = json.getString("name"); rule.name = json.getString("name");
rule.order = json.getInt("order"); rule.order = json.getInt("order");
rule.enabled = json.getBoolean("enabled"); rule.enabled = json.getBoolean("enabled");
rule.daily = json.optBoolean("daily");
rule.stop = json.getBoolean("stop"); rule.stop = json.getBoolean("stop");
rule.condition = json.getString("condition"); rule.condition = json.getString("condition");
rule.action = json.getString("action"); rule.action = json.getString("action");

@ -206,6 +206,7 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr
@Override @Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("enabled", checked).apply(); prefs.edit().putBoolean("enabled", checked).apply();
WorkerDailyRules.init(compoundButton.getContext());
} }
}); });

@ -88,6 +88,7 @@ public class FragmentRule extends FragmentBase {
private EditText etName; private EditText etName;
private EditText etOrder; private EditText etOrder;
private CheckBox cbEnabled; private CheckBox cbEnabled;
private CheckBox cbDaily;
private CheckBox cbStop; private CheckBox cbStop;
private EditText etSender; private EditText etSender;
@ -244,6 +245,7 @@ public class FragmentRule extends FragmentBase {
etName = view.findViewById(R.id.etName); etName = view.findViewById(R.id.etName);
etOrder = view.findViewById(R.id.etOrder); etOrder = view.findViewById(R.id.etOrder);
cbEnabled = view.findViewById(R.id.cbEnabled); cbEnabled = view.findViewById(R.id.cbEnabled);
cbDaily = view.findViewById(R.id.cbDaily);
cbStop = view.findViewById(R.id.cbStop); cbStop = view.findViewById(R.id.cbStop);
etSender = view.findViewById(R.id.etSender); etSender = view.findViewById(R.id.etSender);
@ -1098,6 +1100,7 @@ public class FragmentRule extends FragmentBase {
etName.setText(rule == null ? args.getString("subject") : rule.name); etName.setText(rule == null ? args.getString("subject") : rule.name);
etOrder.setText(rule == null ? null : Integer.toString(rule.order)); etOrder.setText(rule == null ? null : Integer.toString(rule.order));
cbEnabled.setChecked(rule == null || rule.enabled); cbEnabled.setChecked(rule == null || rule.enabled);
cbDaily.setChecked(rule != null && rule.daily);
cbStop.setChecked(rule != null && rule.stop); cbStop.setChecked(rule != null && rule.stop);
etSender.setText(jsender == null ? args.getString("sender") : jsender.getString("value")); etSender.setText(jsender == null ? args.getString("sender") : jsender.getString("value"));
@ -1320,6 +1323,7 @@ public class FragmentRule extends FragmentBase {
args.putString("name", etName.getText().toString()); args.putString("name", etName.getText().toString());
args.putString("order", etOrder.getText().toString()); args.putString("order", etOrder.getText().toString());
args.putBoolean("enabled", cbEnabled.isChecked()); args.putBoolean("enabled", cbEnabled.isChecked());
args.putBoolean("daily", cbDaily.isChecked());
args.putBoolean("stop", cbStop.isChecked()); args.putBoolean("stop", cbStop.isChecked());
args.putString("condition", getCondition().toString()); args.putString("condition", getCondition().toString());
args.putString("action", getAction().toString()); args.putString("action", getAction().toString());
@ -1342,6 +1346,7 @@ public class FragmentRule extends FragmentBase {
String name = args.getString("name"); String name = args.getString("name");
String order = args.getString("order"); String order = args.getString("order");
boolean enabled = args.getBoolean("enabled"); boolean enabled = args.getBoolean("enabled");
boolean daily = args.getBoolean("daily");
boolean stop = args.getBoolean("stop"); boolean stop = args.getBoolean("stop");
String condition = args.getString("condition"); String condition = args.getString("condition");
String action = args.getString("action"); String action = args.getString("action");
@ -1378,6 +1383,7 @@ public class FragmentRule extends FragmentBase {
rule.name = name; rule.name = name;
rule.order = Integer.parseInt(order); rule.order = Integer.parseInt(order);
rule.enabled = enabled; rule.enabled = enabled;
rule.daily = daily;
rule.stop = stop; rule.stop = stop;
rule.condition = condition; rule.condition = condition;
rule.action = action; rule.action = action;
@ -1389,6 +1395,7 @@ public class FragmentRule extends FragmentBase {
rule.name = name; rule.name = name;
rule.order = Integer.parseInt(order); rule.order = Integer.parseInt(order);
rule.enabled = enabled; rule.enabled = enabled;
rule.daily = daily;
rule.stop = stop; rule.stop = stop;
rule.condition = condition; rule.condition = condition;
rule.action = action; rule.action = action;

@ -0,0 +1,152 @@
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 <http://www.gnu.org/licenses/>.
Copyright 2018-2022 by Marcel Bokhorst (M66B)
*/
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.mail.Header;
public class WorkerDailyRules extends Worker {
public WorkerDailyRules(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
Log.i("Instance " + getName());
}
@NonNull
@Override
public Result doWork() {
Thread.currentThread().setPriority(THREAD_PRIORITY_BACKGROUND);
final Context context = getApplicationContext();
try {
EntityLog.log(context, EntityLog.Type.Rules, "Running daily rules");
DB db = DB.getInstance(context);
List<EntityAccount> accounts = db.account().getSynchronizingAccounts(null);
for (EntityAccount account : accounts) {
List<EntityFolder> folders = db.folder().getFolders(account.id, false, false);
for (EntityFolder folder : folders) {
List<EntityRule> rules = db.rule().getEnabledRules(folder.id);
int daily = 0;
for (EntityRule rule : rules)
if (rule.daily)
daily++;
if (daily == 0)
continue;
int count = 0;
List<Long> mids = db.message().getMessageIdsByFolder(folder.id);
for (long mid : mids) {
EntityMessage message = db.message().getMessage(mid);
if (message == null)
continue;
count++;
boolean needsHeaders = EntityRule.needsHeaders(message, rules);
boolean needsBody = EntityRule.needsBody(message, rules);
if (needsHeaders && message.headers == null) {
EntityOperation.queue(context, message, EntityOperation.HEADERS);
continue;
}
if (needsBody && !message.content) {
EntityOperation.queue(context, message, EntityOperation.BODY);
continue;
}
for (EntityRule rule : rules)
if (rule.daily &&
rule.matches(context, message, null, null)) {
rule.execute(context, message);
if (rule.stop)
break;
}
}
EntityLog.log(context, EntityLog.Type.Rules, folder,
"Executed " + count + " rules for " + account.name + "/" + folder.name);
}
}
EntityLog.log(context, EntityLog.Type.Rules, "Completed daily rules");
return Result.success();
} catch (Throwable ex) {
Log.e(ex);
return Result.failure();
}
}
static void init(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean enabled = prefs.getBoolean("enabled", true);
try {
if (enabled) {
Calendar cal = Calendar.getInstance();
long delay = cal.getTimeInMillis();
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.HOUR_OF_DAY, 1);
cal.add(Calendar.DAY_OF_MONTH, 1);
delay = cal.getTimeInMillis() - delay;
Log.i("Queuing " + getName() + " delay=" + (delay / (60 * 1000L)) + "m");
PeriodicWorkRequest.Builder builder =
new PeriodicWorkRequest.Builder(WorkerAutoUpdate.class, 1, TimeUnit.DAYS)
.setInitialDelay(delay, TimeUnit.MILLISECONDS);
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(getName(), ExistingPeriodicWorkPolicy.KEEP, builder.build());
Log.i("Queued " + getName());
} else {
Log.i("Cancelling " + getName());
WorkManager.getInstance(context).cancelUniqueWork(getName());
Log.i("Cancelled " + getName());
}
} catch (IllegalStateException ex) {
// https://issuetracker.google.com/issues/138465476
Log.w(ex);
}
}
private static String getName() {
return WorkerAutoUpdate.class.getSimpleName();
}
}

@ -101,6 +101,15 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etOrder" /> app:layout_constraintTop_toBottomOf="@id/etOrder" />
<CheckBox
android:id="@+id/cbDaily"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_rule_daily"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbEnabled" />
<CheckBox <CheckBox
android:id="@+id/cbStop" android:id="@+id/cbStop"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -108,7 +117,7 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:text="@string/title_rule_stop" android:text="@string/title_rule_stop"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbEnabled" /> app:layout_constraintTop_toBottomOf="@id/cbDaily" />
<!-- condition --> <!-- condition -->

@ -16,6 +16,16 @@
android:foreground="?attr/selectableItemBackground" android:foreground="?attr/selectableItemBackground"
android:padding="6dp"> android:padding="6dp">
<ImageView
android:id="@+id/ivDaily"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="@string/title_rule_daily"
app:layout_constraintBottom_toBottomOf="@+id/ivStop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/ivStop"
app:srcCompat="@drawable/twotone_hourglass_top_24" />
<TextView <TextView
android:id="@+id/tvName" android:id="@+id/tvName"
android:layout_width="0dp" android:layout_width="0dp"
@ -27,7 +37,7 @@
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintBottom_toBottomOf="@+id/ivStop" app:layout_constraintBottom_toBottomOf="@+id/ivStop"
app:layout_constraintEnd_toStartOf="@+id/tvOrder" app:layout_constraintEnd_toStartOf="@+id/tvOrder"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@id/ivDaily"
app:layout_constraintTop_toTopOf="@+id/ivStop" /> app:layout_constraintTop_toTopOf="@+id/ivStop" />
<TextView <TextView

@ -1744,6 +1744,7 @@
<string name="title_rule_name">Name</string> <string name="title_rule_name">Name</string>
<string name="title_rule_order">Order</string> <string name="title_rule_order">Order</string>
<string name="title_rule_enabled">Enabled</string> <string name="title_rule_enabled">Enabled</string>
<string name="title_rule_daily">Run daily (only)</string>
<string name="title_rule_stop">Stop processing rules after executing this rule</string> <string name="title_rule_stop">Stop processing rules after executing this rule</string>
<string name="title_rule_sender">Sender contains</string> <string name="title_rule_sender">Sender contains</string>
<string name="title_rule_sender_known">Sender is a contact</string> <string name="title_rule_sender_known">Sender is a contact</string>

Loading…
Cancel
Save