diff --git a/app/schemas/eu.faircode.email.DB/38.json b/app/schemas/eu.faircode.email.DB/38.json
new file mode 100644
index 0000000000..f5ab24a666
--- /dev/null
+++ b/app/schemas/eu.faircode.email.DB/38.json
@@ -0,0 +1,1423 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 38,
+ "identityHash": "29810bddf03ce014bfb3fde99a3f963c",
+ "entities": [
+ {
+ "tableName": "identity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `account` INTEGER NOT NULL, `display` TEXT, `color` INTEGER, `signature` TEXT, `auth_type` INTEGER NOT NULL, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `realm` TEXT, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `replyto` TEXT, `bcc` TEXT, `delivery_receipt` INTEGER NOT NULL, `read_receipt` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `sent_folder` INTEGER, `tbd` INTEGER, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "display",
+ "columnName": "display",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "signature",
+ "columnName": "signature",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "host",
+ "columnName": "host",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "starttls",
+ "columnName": "starttls",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "insecure",
+ "columnName": "insecure",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "user",
+ "columnName": "user",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realm",
+ "columnName": "realm",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyto",
+ "columnName": "replyto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bcc",
+ "columnName": "bcc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "delivery_receipt",
+ "columnName": "delivery_receipt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "read_receipt",
+ "columnName": "read_receipt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "store_sent",
+ "columnName": "store_sent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sent_folder",
+ "columnName": "sent_folder",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tbd",
+ "columnName": "tbd",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_identity_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
+ },
+ {
+ "name": "index_identity_account_email",
+ "unique": false,
+ "columnNames": [
+ "account",
+ "email"
+ ],
+ "createSql": "CREATE INDEX `index_identity_account_email` ON `${TABLE_NAME}` (`account`, `email`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "account",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `auth_type` INTEGER NOT NULL, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `realm` TEXT, `name` TEXT, `signature` TEXT, `color` INTEGER, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `notify` INTEGER NOT NULL, `browse` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `prefix` TEXT, `created` INTEGER, `tbd` INTEGER, `state` TEXT, `error` TEXT, `last_connected` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "host",
+ "columnName": "host",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "starttls",
+ "columnName": "starttls",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "insecure",
+ "columnName": "insecure",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "user",
+ "columnName": "user",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "realm",
+ "columnName": "realm",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "signature",
+ "columnName": "signature",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notify",
+ "columnName": "notify",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "browse",
+ "columnName": "browse",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll_interval",
+ "columnName": "poll_interval",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "prefix",
+ "columnName": "prefix",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "created",
+ "columnName": "created",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tbd",
+ "columnName": "tbd",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "last_connected",
+ "columnName": "last_connected",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "folder",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `level` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `poll` INTEGER NOT NULL, `download` INTEGER NOT NULL, `sync_days` INTEGER NOT NULL, `keep_days` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `notify` INTEGER NOT NULL, `keywords` TEXT, `initialize` INTEGER NOT NULL, `tbc` INTEGER, `tbd` INTEGER, `state` TEXT, `sync_state` TEXT, `error` TEXT, `last_sync` INTEGER, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll",
+ "columnName": "poll",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "download",
+ "columnName": "download",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sync_days",
+ "columnName": "sync_days",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep_days",
+ "columnName": "keep_days",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "display",
+ "columnName": "display",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hide",
+ "columnName": "hide",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unified",
+ "columnName": "unified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notify",
+ "columnName": "notify",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keywords",
+ "columnName": "keywords",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "initialize",
+ "columnName": "initialize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tbc",
+ "columnName": "tbc",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tbd",
+ "columnName": "tbd",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sync_state",
+ "columnName": "sync_state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "last_sync",
+ "columnName": "last_sync",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_folder_account_name",
+ "unique": true,
+ "columnNames": [
+ "account",
+ "name"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
+ },
+ {
+ "name": "index_folder_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
+ },
+ {
+ "name": "index_folder_name",
+ "unique": false,
+ "columnNames": [
+ "name"
+ ],
+ "createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
+ },
+ {
+ "name": "index_folder_type",
+ "unique": false,
+ "columnNames": [
+ "type"
+ ],
+ "createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
+ },
+ {
+ "name": "index_folder_unified",
+ "unique": false,
+ "columnNames": [
+ "unified"
+ ],
+ "createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "message",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER NOT NULL, `folder` INTEGER NOT NULL, `identity` INTEGER, `extra` TEXT, `replying` INTEGER, `forwarding` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `deliveredto` TEXT, `inreplyto` TEXT, `thread` TEXT, `avatar` TEXT, `sender` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `raw` INTEGER, `subject` TEXT, `size` INTEGER, `content` INTEGER NOT NULL, `preview` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `answered` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `keywords` TEXT, `ui_seen` INTEGER NOT NULL, `ui_answered` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `ui_ignored` INTEGER NOT NULL, `ui_browsed` INTEGER NOT NULL, `ui_snoozed` INTEGER, `warning` TEXT, `error` TEXT, `last_attempt` INTEGER, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`forwarding`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "folder",
+ "columnName": "folder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "identity",
+ "columnName": "identity",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "extra",
+ "columnName": "extra",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replying",
+ "columnName": "replying",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "forwarding",
+ "columnName": "forwarding",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "msgid",
+ "columnName": "msgid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "references",
+ "columnName": "references",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "deliveredto",
+ "columnName": "deliveredto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inreplyto",
+ "columnName": "inreplyto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thread",
+ "columnName": "thread",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "avatar",
+ "columnName": "avatar",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sender",
+ "columnName": "sender",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "from",
+ "columnName": "from",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "to",
+ "columnName": "to",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cc",
+ "columnName": "cc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bcc",
+ "columnName": "bcc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reply",
+ "columnName": "reply",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "headers",
+ "columnName": "headers",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "raw",
+ "columnName": "raw",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "size",
+ "columnName": "size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "preview",
+ "columnName": "preview",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sent",
+ "columnName": "sent",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "received",
+ "columnName": "received",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stored",
+ "columnName": "stored",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen",
+ "columnName": "seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "answered",
+ "columnName": "answered",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "flagged",
+ "columnName": "flagged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keywords",
+ "columnName": "keywords",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ui_seen",
+ "columnName": "ui_seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_answered",
+ "columnName": "ui_answered",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_flagged",
+ "columnName": "ui_flagged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_hide",
+ "columnName": "ui_hide",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_found",
+ "columnName": "ui_found",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_ignored",
+ "columnName": "ui_ignored",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_browsed",
+ "columnName": "ui_browsed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_snoozed",
+ "columnName": "ui_snoozed",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "warning",
+ "columnName": "warning",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "last_attempt",
+ "columnName": "last_attempt",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_message_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
+ },
+ {
+ "name": "index_message_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_message_identity",
+ "unique": false,
+ "columnNames": [
+ "identity"
+ ],
+ "createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
+ },
+ {
+ "name": "index_message_replying",
+ "unique": false,
+ "columnNames": [
+ "replying"
+ ],
+ "createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
+ },
+ {
+ "name": "index_message_forwarding",
+ "unique": false,
+ "columnNames": [
+ "forwarding"
+ ],
+ "createSql": "CREATE INDEX `index_message_forwarding` ON `${TABLE_NAME}` (`forwarding`)"
+ },
+ {
+ "name": "index_message_folder_uid",
+ "unique": true,
+ "columnNames": [
+ "folder",
+ "uid"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
+ },
+ {
+ "name": "index_message_msgid_folder",
+ "unique": true,
+ "columnNames": [
+ "msgid",
+ "folder"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
+ },
+ {
+ "name": "index_message_thread",
+ "unique": false,
+ "columnNames": [
+ "thread"
+ ],
+ "createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
+ },
+ {
+ "name": "index_message_sender",
+ "unique": false,
+ "columnNames": [
+ "sender"
+ ],
+ "createSql": "CREATE INDEX `index_message_sender` ON `${TABLE_NAME}` (`sender`)"
+ },
+ {
+ "name": "index_message_received",
+ "unique": false,
+ "columnNames": [
+ "received"
+ ],
+ "createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
+ },
+ {
+ "name": "index_message_ui_seen",
+ "unique": false,
+ "columnNames": [
+ "ui_seen"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
+ },
+ {
+ "name": "index_message_ui_flagged",
+ "unique": false,
+ "columnNames": [
+ "ui_flagged"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_flagged` ON `${TABLE_NAME}` (`ui_flagged`)"
+ },
+ {
+ "name": "index_message_ui_hide",
+ "unique": false,
+ "columnNames": [
+ "ui_hide"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
+ },
+ {
+ "name": "index_message_ui_found",
+ "unique": false,
+ "columnNames": [
+ "ui_found"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
+ },
+ {
+ "name": "index_message_ui_ignored",
+ "unique": false,
+ "columnNames": [
+ "ui_ignored"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_ignored` ON `${TABLE_NAME}` (`ui_ignored`)"
+ },
+ {
+ "name": "index_message_ui_browsed",
+ "unique": false,
+ "columnNames": [
+ "ui_browsed"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_browsed` ON `${TABLE_NAME}` (`ui_browsed`)"
+ },
+ {
+ "name": "index_message_ui_snoozed",
+ "unique": false,
+ "columnNames": [
+ "ui_snoozed"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_snoozed` ON `${TABLE_NAME}` (`ui_snoozed`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "identity",
+ "onDelete": "SET NULL",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "identity"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "message",
+ "onDelete": "SET NULL",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "replying"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "message",
+ "onDelete": "SET NULL",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "forwarding"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "attachment",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `disposition` TEXT, `cid` TEXT, `encryption` INTEGER, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sequence",
+ "columnName": "sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "disposition",
+ "columnName": "disposition",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cid",
+ "columnName": "cid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "encryption",
+ "columnName": "encryption",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "size",
+ "columnName": "size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "progress",
+ "columnName": "progress",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "available",
+ "columnName": "available",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_attachment_message",
+ "unique": false,
+ "columnNames": [
+ "message"
+ ],
+ "createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
+ },
+ {
+ "name": "index_attachment_message_sequence",
+ "unique": true,
+ "columnNames": [
+ "message",
+ "sequence"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
+ },
+ {
+ "name": "index_attachment_message_cid",
+ "unique": true,
+ "columnNames": [
+ "message",
+ "cid"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_attachment_message_cid` ON `${TABLE_NAME}` (`message`, `cid`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "message"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "operation",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "folder",
+ "columnName": "folder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "args",
+ "columnName": "args",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "created",
+ "columnName": "created",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_operation_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_operation_message",
+ "unique": false,
+ "columnNames": [
+ "message"
+ ],
+ "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "message"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "answer",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "rule",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `name` TEXT NOT NULL, `order` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `stop` INTEGER NOT NULL, `condition` TEXT NOT NULL, `action` TEXT NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "folder",
+ "columnName": "folder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "order",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enabled",
+ "columnName": "enabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stop",
+ "columnName": "stop",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "condition",
+ "columnName": "condition",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "action",
+ "columnName": "action",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_rule_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_rule_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_rule_order",
+ "unique": false,
+ "columnNames": [
+ "order"
+ ],
+ "createSql": "CREATE INDEX `index_rule_order` ON `${TABLE_NAME}` (`order`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "log",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_log_time",
+ "unique": false,
+ "columnNames": [
+ "time"
+ ],
+ "createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"29810bddf03ce014bfb3fde99a3f963c\")"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java
index d93234e325..cad07090a0 100644
--- a/app/src/main/java/eu/faircode/email/DB.java
+++ b/app/src/main/java/eu/faircode/email/DB.java
@@ -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();
}
diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java
index 1cc1441d78..596f2be288 100644
--- a/app/src/main/java/eu/faircode/email/EntityRule.java
+++ b/app/src/main/java/eu/faircode/email/EntityRule.java
@@ -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
diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java
index 471b77beff..103f10365e 100644
--- a/app/src/main/java/eu/faircode/email/FragmentRule.java
+++ b/app/src/main/java/eu/faircode/email/FragmentRule.java
@@ -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);
+ JSONObject jcondition = new JSONObject();
+
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 jcondition = new JSONObject();
- if (!TextUtils.isEmpty(sender))
- jcondition.put("sender", sender);
- if (!TextUtils.isEmpty(subject))
- jcondition.put("subject", subject);
+ JSONObject jaction = new JSONObject();
Action action = (Action) spAction.getSelectedItem();
-
- JSONObject jaction = new JSONObject();
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);
diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
index 70368c3b9c..59099e1f6e 100644
--- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
+++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
@@ -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;
}
diff --git a/app/src/main/res/layout/fragment_rule.xml b/app/src/main/res/layout/fragment_rule.xml
index 0e6d36c735..384e15c463 100644
--- a/app/src/main/res/layout/fragment_rule.xml
+++ b/app/src/main/res/layout/fragment_rule.xml
@@ -67,17 +67,38 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etOrder" />
+
+
+ app:layout_constraintTop_toTopOf="@+id/cbSender" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/cbSender" />
+
+
+ app:layout_constraintTop_toTopOf="@+id/cbSubject" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/cbSubject" />
+ app:layout_constraintTop_toBottomOf="@+id/etSubject" />
@@ -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" />
Name
Order
Enabled
- Sender
- Subject
+ Stop processing
+ Sender contains
+ Subject contains
+ Header contains
+ Regex
+ AND
Action
- Target
+ To
Mark as read
- Folder
Rule name missing
+ Condition missing
Mark read
Archive