From daadc58e97eb08a38ff5543b8dca435944eab796 Mon Sep 17 00:00:00 2001 From: M66B Date: Tue, 19 Nov 2019 21:53:12 +0100 Subject: [PATCH] Revised revisions --- app/schemas/eu.faircode.email.DB/115.json | 1914 +++++++++++++++++ .../java/eu/faircode/email/ActivityEML.java | 8 +- .../eu/faircode/email/AdapterMessage.java | 17 +- app/src/main/java/eu/faircode/email/DB.java | 12 +- .../java/eu/faircode/email/DaoRevision.java | 35 - .../eu/faircode/email/EditTextCompose.java | 6 +- .../java/eu/faircode/email/EntityMessage.java | 6 +- .../eu/faircode/email/EntityRevision.java | 63 - .../eu/faircode/email/FragmentCompose.java | 534 +++-- .../faircode/email/FragmentOptionsMisc.java | 2 +- .../java/eu/faircode/email/HtmlHelper.java | 13 +- .../java/eu/faircode/email/MessageHelper.java | 46 +- app/src/main/res/layout/fragment_compose.xml | 17 +- app/src/main/res/values/strings.xml | 1 - 14 files changed, 2225 insertions(+), 449 deletions(-) create mode 100644 app/schemas/eu.faircode.email.DB/115.json delete mode 100644 app/src/main/java/eu/faircode/email/DaoRevision.java delete mode 100644 app/src/main/java/eu/faircode/email/EntityRevision.java diff --git a/app/schemas/eu.faircode.email.DB/115.json b/app/schemas/eu.faircode.email.DB/115.json new file mode 100644 index 0000000000..b15d7321b8 --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/115.json @@ -0,0 +1,1914 @@ +{ + "formatVersion": 1, + "database": { + "version": 115, + "identityHash": "b2a926b86539599837d566acd5f4421d", + "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, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `auth_type` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `realm` TEXT, `use_ip` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `sender_extra` INTEGER NOT NULL, `sender_extra_regex` TEXT, `replyto` TEXT, `bcc` TEXT, `plain_only` INTEGER NOT NULL, `encrypt` INTEGER NOT NULL, `delivery_receipt` INTEGER NOT NULL, `read_receipt` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `sent_folder` INTEGER, `sign_key` INTEGER, `tbd` INTEGER, `state` TEXT, `error` TEXT, `last_connected` INTEGER, 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": "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": "auth_type", + "columnName": "auth_type", + "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": "use_ip", + "columnName": "use_ip", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender_extra", + "columnName": "sender_extra", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender_extra_regex", + "columnName": "sender_extra_regex", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "replyto", + "columnName": "replyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bcc", + "columnName": "bcc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "plain_only", + "columnName": "plain_only", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "encrypt", + "columnName": "encrypt", + "affinity": "INTEGER", + "notNull": true + }, + { + "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": "sign_key", + "columnName": "sign_key", + "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": [ + { + "name": "index_identity_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_identity_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_identity_account_email", + "unique": false, + "columnNames": [ + "account", + "email" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `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}` (`order` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `pop` INTEGER NOT NULL, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `auth_type` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `realm` TEXT, `name` TEXT, `signature` TEXT, `color` INTEGER, `synchronize` INTEGER NOT NULL, `ondemand` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `notify` INTEGER NOT NULL, `browse` INTEGER NOT NULL, `auto_seen` INTEGER NOT NULL, `separator` INTEGER, `swipe_left` INTEGER, `swipe_right` INTEGER, `move_to` INTEGER, `poll_interval` INTEGER NOT NULL, `partial_fetch` INTEGER NOT NULL, `ignore_size` INTEGER NOT NULL, `prefix` TEXT, `created` INTEGER, `tbd` INTEGER, `state` TEXT, `warning` TEXT, `error` TEXT, `last_connected` INTEGER)", + "fields": [ + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "pop", + "columnName": "pop", + "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": "auth_type", + "columnName": "auth_type", + "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": "ondemand", + "columnName": "ondemand", + "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": "auto_seen", + "columnName": "auto_seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "separator", + "columnName": "separator", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "swipe_left", + "columnName": "swipe_left", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "swipe_right", + "columnName": "swipe_right", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "move_to", + "columnName": "move_to", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "poll_interval", + "columnName": "poll_interval", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "partial_fetch", + "columnName": "partial_fetch", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ignore_size", + "columnName": "ignore_size", + "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": "warning", + "columnName": "warning", + "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}` (`order` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `parent` INTEGER, `uidv` 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, `subscribed` INTEGER, `sync_days` INTEGER NOT NULL, `keep_days` INTEGER NOT NULL, `auto_delete` INTEGER NOT NULL, `display` TEXT, `color` INTEGER, `hide` INTEGER NOT NULL, `collapsed` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `navigation` INTEGER NOT NULL, `notify` INTEGER NOT NULL, `total` INTEGER, `keywords` TEXT, `initialize` INTEGER NOT NULL, `tbc` INTEGER, `tbd` INTEGER, `rename` TEXT, `state` TEXT, `sync_state` TEXT, `read_only` INTEGER NOT NULL, `selectable` INTEGER NOT NULL, `error` TEXT, `last_sync` INTEGER, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uidv", + "columnName": "uidv", + "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": "subscribed", + "columnName": "subscribed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sync_days", + "columnName": "sync_days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep_days", + "columnName": "keep_days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "auto_delete", + "columnName": "auto_delete", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "display", + "columnName": "display", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hide", + "columnName": "hide", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "collapsed", + "columnName": "collapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unified", + "columnName": "unified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "navigation", + "columnName": "navigation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notify", + "columnName": "notify", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "total", + "columnName": "total", + "affinity": "INTEGER", + "notNull": false + }, + { + "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": "rename", + "columnName": "rename", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sync_state", + "columnName": "sync_state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "read_only", + "columnName": "read_only", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "selectable", + "columnName": "selectable", + "affinity": "INTEGER", + "notNull": true + }, + { + "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 IF NOT EXISTS `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)" + }, + { + "name": "index_folder_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_folder_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_folder_name", + "unique": false, + "columnNames": [ + "name" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_folder_name` ON `${TABLE_NAME}` (`name`)" + }, + { + "name": "index_folder_type", + "unique": false, + "columnNames": [ + "type" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_folder_type` ON `${TABLE_NAME}` (`type`)" + }, + { + "name": "index_folder_unified", + "unique": false, + "columnNames": [ + "unified" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `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, `priority` INTEGER, `receipt` INTEGER, `receipt_request` INTEGER, `receipt_to` TEXT, `dkim` INTEGER, `spf` INTEGER, `dmarc` INTEGER, `mx` INTEGER, `avatar` TEXT, `sender` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `list_post` TEXT, `unsubscribe` TEXT, `headers` TEXT, `raw` INTEGER, `subject` TEXT, `size` INTEGER, `total` INTEGER, `attachments` INTEGER NOT NULL, `content` INTEGER NOT NULL, `plain_only` INTEGER, `encrypt` INTEGER, `preview` TEXT, `signature` INTEGER NOT NULL, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `answered` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `flags` TEXT, `keywords` TEXT, `notifying` INTEGER NOT NULL, `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_busy` INTEGER, `ui_snoozed` INTEGER, `color` INTEGER, `revision` INTEGER, `revisions` 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": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "receipt", + "columnName": "receipt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "receipt_request", + "columnName": "receipt_request", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "receipt_to", + "columnName": "receipt_to", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dkim", + "columnName": "dkim", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "spf", + "columnName": "spf", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dmarc", + "columnName": "dmarc", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "mx", + "columnName": "mx", + "affinity": "INTEGER", + "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": "list_post", + "columnName": "list_post", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "unsubscribe", + "columnName": "unsubscribe", + "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": "total", + "columnName": "total", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "plain_only", + "columnName": "plain_only", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "encrypt", + "columnName": "encrypt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "preview", + "columnName": "preview", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "INTEGER", + "notNull": true + }, + { + "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": "flags", + "columnName": "flags", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "keywords", + "columnName": "keywords", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "notifying", + "columnName": "notifying", + "affinity": "INTEGER", + "notNull": true + }, + { + "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_busy", + "columnName": "ui_busy", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "ui_snoozed", + "columnName": "ui_snoozed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "revision", + "columnName": "revision", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "revisions", + "columnName": "revisions", + "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 IF NOT EXISTS `index_message_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_message_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_message_identity", + "unique": false, + "columnNames": [ + "identity" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_identity` ON `${TABLE_NAME}` (`identity`)" + }, + { + "name": "index_message_folder_uid", + "unique": true, + "columnNames": [ + "folder", + "uid" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)" + }, + { + "name": "index_message_msgid", + "unique": false, + "columnNames": [ + "msgid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_msgid` ON `${TABLE_NAME}` (`msgid`)" + }, + { + "name": "index_message_thread", + "unique": false, + "columnNames": [ + "thread" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_thread` ON `${TABLE_NAME}` (`thread`)" + }, + { + "name": "index_message_sender", + "unique": false, + "columnNames": [ + "sender" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_sender` ON `${TABLE_NAME}` (`sender`)" + }, + { + "name": "index_message_received", + "unique": false, + "columnNames": [ + "received" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_received` ON `${TABLE_NAME}` (`received`)" + }, + { + "name": "index_message_subject", + "unique": false, + "columnNames": [ + "subject" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_subject` ON `${TABLE_NAME}` (`subject`)" + }, + { + "name": "index_message_ui_seen", + "unique": false, + "columnNames": [ + "ui_seen" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)" + }, + { + "name": "index_message_ui_flagged", + "unique": false, + "columnNames": [ + "ui_flagged" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_ui_flagged` ON `${TABLE_NAME}` (`ui_flagged`)" + }, + { + "name": "index_message_ui_hide", + "unique": false, + "columnNames": [ + "ui_hide" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)" + }, + { + "name": "index_message_ui_found", + "unique": false, + "columnNames": [ + "ui_found" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)" + }, + { + "name": "index_message_ui_ignored", + "unique": false, + "columnNames": [ + "ui_ignored" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_ui_ignored` ON `${TABLE_NAME}` (`ui_ignored`)" + }, + { + "name": "index_message_ui_browsed", + "unique": false, + "columnNames": [ + "ui_browsed" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_ui_browsed` ON `${TABLE_NAME}` (`ui_browsed`)" + }, + { + "name": "index_message_ui_snoozed", + "unique": false, + "columnNames": [ + "ui_snoozed" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `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 IF NOT EXISTS `index_attachment_message` ON `${TABLE_NAME}` (`message`)" + }, + { + "name": "index_attachment_message_sequence", + "unique": true, + "columnNames": [ + "message", + "sequence" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)" + }, + { + "name": "index_attachment_message_cid", + "unique": false, + "columnNames": [ + "message", + "cid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `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, `account` INTEGER, `folder` INTEGER NOT NULL, `message` INTEGER, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, `state` TEXT, `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": "account", + "columnName": "account", + "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": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_operation_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_operation_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_operation_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_operation_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_operation_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_operation_message` ON `${TABLE_NAME}` (`message`)" + }, + { + "name": "index_operation_name", + "unique": false, + "columnNames": [ + "name" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_operation_name` ON `${TABLE_NAME}` (`name`)" + }, + { + "name": "index_operation_state", + "unique": false, + "columnNames": [ + "state" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_operation_state` ON `${TABLE_NAME}` (`state`)" + } + ], + "foreignKeys": [ + { + "table": "folder", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "folder" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "message" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "contact", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER NOT NULL, `type` INTEGER NOT NULL, `email` TEXT NOT NULL, `name` TEXT, `avatar` TEXT, `times_contacted` INTEGER NOT NULL, `first_contacted` INTEGER NOT NULL, `last_contacted` INTEGER NOT NULL, `state` INTEGER NOT NULL, 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": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "times_contacted", + "columnName": "times_contacted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "first_contacted", + "columnName": "first_contacted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "last_contacted", + "columnName": "last_contacted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_contact_account_type_email", + "unique": true, + "columnNames": [ + "account", + "type", + "email" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_contact_account_type_email` ON `${TABLE_NAME}` (`account`, `type`, `email`)" + }, + { + "name": "index_contact_email", + "unique": false, + "columnNames": [ + "email" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_contact_email` ON `${TABLE_NAME}` (`email`)" + }, + { + "name": "index_contact_name", + "unique": false, + "columnNames": [ + "name" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_contact_name` ON `${TABLE_NAME}` (`name`)" + }, + { + "name": "index_contact_avatar", + "unique": false, + "columnNames": [ + "avatar" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_contact_avatar` ON `${TABLE_NAME}` (`avatar`)" + }, + { + "name": "index_contact_times_contacted", + "unique": false, + "columnNames": [ + "times_contacted" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_contact_times_contacted` ON `${TABLE_NAME}` (`times_contacted`)" + }, + { + "name": "index_contact_last_contacted", + "unique": false, + "columnNames": [ + "last_contacted" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_contact_last_contacted` ON `${TABLE_NAME}` (`last_contacted`)" + }, + { + "name": "index_contact_state", + "unique": false, + "columnNames": [ + "state" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_contact_state` ON `${TABLE_NAME}` (`state`)" + } + ], + "foreignKeys": [ + { + "table": "account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "answer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `hide` INTEGER NOT NULL, `text` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hide", + "columnName": "hide", + "affinity": "INTEGER", + "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, `applied` INTEGER 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 + }, + { + "fieldPath": "applied", + "columnName": "applied", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_rule_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_rule_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_rule_order", + "unique": false, + "columnNames": [ + "order" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `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 IF NOT EXISTS `index_log_time` ON `${TABLE_NAME}` (`time`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "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, 'b2a926b86539599837d566acd5f4421d')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/ActivityEML.java b/app/src/main/java/eu/faircode/email/ActivityEML.java index 14f76a36e1..510d2ab0c2 100644 --- a/app/src/main/java/eu/faircode/email/ActivityEML.java +++ b/app/src/main/java/eu/faircode/email/ActivityEML.java @@ -48,6 +48,8 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; import com.sun.mail.imap.IMAPFolder; +import org.jsoup.nodes.Document; + import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; @@ -135,8 +137,10 @@ public class ActivityEML extends ActivityBase { result.parts = helper.getMessageParts(); String html = result.parts.getHtml(context); - if (html != null) - result.body = HtmlHelper.fromHtml(HtmlHelper.sanitize(context, html, false, false)); + if (html != null) { + Document document = HtmlHelper.sanitize(context, html, false, false); + result.body = HtmlHelper.fromHtml(document.html()); + } return result; } diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 26cddedbe7..4771a32acc 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -1455,28 +1455,27 @@ public class AdapterMessage extends RecyclerView.Adapter", lines) + ""; + Element pre = document.createElement("pre"); + pre.html(TextUtils.join("
", lines)); + document.appendChild(pre); } // Draw images - Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() { + Spanned spanned = HtmlHelper.fromHtml(document.html(), new Html.ImageGetter() { @Override public Drawable getDrawable(String source) { Drawable drawable = ImageHelper.decodeImage(context, message.id, source, show_images, zoom, tvBody); diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java index 3d2ac11fb4..8a08712482 100644 --- a/app/src/main/java/eu/faircode/email/DB.java +++ b/app/src/main/java/eu/faircode/email/DB.java @@ -56,13 +56,12 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory; // https://developer.android.com/topic/libraries/architecture/room.html @Database( - version = 114, + version = 115, entities = { EntityIdentity.class, EntityAccount.class, EntityFolder.class, EntityMessage.class, - EntityRevision.class, EntityAttachment.class, EntityOperation.class, EntityContact.class, @@ -84,8 +83,6 @@ public abstract class DB extends RoomDatabase { public abstract DaoMessage message(); - public abstract DaoRevision revision(); - public abstract DaoAttachment attachment(); public abstract DaoOperation operation(); @@ -1113,6 +1110,13 @@ public abstract class DB extends RoomDatabase { " WHERE encryption = " + EntityAttachment.PGP_MESSAGE + ")"); } }) + .addMigrations(new Migration(114, 115) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + Log.i("DB migration from version " + startVersion + " to " + endVersion); + db.execSQL("DROP TABLE revision"); + } + }) .build(); } diff --git a/app/src/main/java/eu/faircode/email/DaoRevision.java b/app/src/main/java/eu/faircode/email/DaoRevision.java deleted file mode 100644 index 4904828132..0000000000 --- a/app/src/main/java/eu/faircode/email/DaoRevision.java +++ /dev/null @@ -1,35 +0,0 @@ -package eu.faircode.email; - -/* - This file is part of FairEmail. - - FairEmail is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - FairEmail is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with FairEmail. If not, see . - - Copyright 2018-2019 by Marcel Bokhorst (M66B) -*/ - -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.Query; - -@Dao -public interface DaoRevision { - @Query("SELECT * FROM revision" + - " WHERE message = :message" + - " AND sequence = :sequence") - EntityRevision getRevision(long message, int sequence); - - @Insert - long insertRevision(EntityRevision revision); -} diff --git a/app/src/main/java/eu/faircode/email/EditTextCompose.java b/app/src/main/java/eu/faircode/email/EditTextCompose.java index 238d3f0dd3..6b56870c09 100644 --- a/app/src/main/java/eu/faircode/email/EditTextCompose.java +++ b/app/src/main/java/eu/faircode/email/EditTextCompose.java @@ -37,6 +37,8 @@ import androidx.core.view.inputmethod.EditorInfoCompat; import androidx.core.view.inputmethod.InputConnectionCompat; import androidx.core.view.inputmethod.InputContentInfoCompat; +import org.jsoup.nodes.Document; + public class EditTextCompose extends AppCompatEditText { private ISelection selectionListener = null; private IInputContentListener inputContentListener = null; @@ -70,8 +72,8 @@ public class EditTextCompose extends AppCompatEditText { ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); String html = item.coerceToHtmlText(context); - html = HtmlHelper.sanitize(context, html, false, false); - Spanned paste = HtmlHelper.fromHtml(html); + Document document = HtmlHelper.sanitize(context, html, false, false); + Spanned paste = HtmlHelper.fromHtml(document.html()); int colorPrimary = Helper.resolveColor(context, R.attr.colorPrimary); diff --git a/app/src/main/java/eu/faircode/email/EntityMessage.java b/app/src/main/java/eu/faircode/email/EntityMessage.java index cebad26888..409e84b0e4 100644 --- a/app/src/main/java/eu/faircode/email/EntityMessage.java +++ b/app/src/main/java/eu/faircode/email/EntityMessage.java @@ -228,17 +228,13 @@ public class EntityMessage implements Serializable { return new File(dir, id + "." + revision); } - static File getRefFile(Context context, Long id) { + File getRefFile(Context context) { File dir = new File(context.getFilesDir(), "references"); if (!dir.exists()) dir.mkdir(); return new File(dir, id.toString()); } - File getRefFile(Context context) { - return getRefFile(context, id); - } - File getRawFile(Context context) { File dir = new File(context.getFilesDir(), "raw"); if (!dir.exists()) diff --git a/app/src/main/java/eu/faircode/email/EntityRevision.java b/app/src/main/java/eu/faircode/email/EntityRevision.java deleted file mode 100644 index 3d408cc9fd..0000000000 --- a/app/src/main/java/eu/faircode/email/EntityRevision.java +++ /dev/null @@ -1,63 +0,0 @@ -package eu.faircode.email; - -/* - This file is part of FairEmail. - - FairEmail is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - FairEmail is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with FairEmail. If not, see . - - Copyright 2018-2019 by Marcel Bokhorst (M66B) -*/ - -import androidx.annotation.NonNull; -import androidx.room.Entity; -import androidx.room.ForeignKey; -import androidx.room.Index; -import androidx.room.PrimaryKey; - -import static androidx.room.ForeignKey.CASCADE; - -@Entity( - tableName = EntityRevision.TABLE_NAME, - foreignKeys = { - @ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE), - }, - indices = { - @Index(value = {"message"}), - @Index(value = {"message", "sequence"}, unique = true) - } -) -public class EntityRevision { - static final String TABLE_NAME = "revision"; - - @PrimaryKey(autoGenerate = true) - public Long id; - @NonNull - public Long message; - @NonNull - public Integer sequence; - @NonNull - public Boolean reference; - - - @Override - public boolean equals(Object obj) { - if (obj instanceof EntityRevision) { - EntityRevision other = (EntityRevision) obj; - return (this.message.equals(other.message) && - this.sequence.equals(other.sequence) && - this.reference.equals(other.reference)); - } else - return false; - } -} diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 9f6a6b62c9..19f7da2829 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -103,10 +103,8 @@ import com.google.android.material.snackbar.Snackbar; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; -import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; -import org.jsoup.select.NodeTraversor; -import org.jsoup.select.NodeVisitor; +import org.jsoup.select.Elements; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; @@ -168,7 +166,6 @@ public class FragmentCompose extends FragmentBase { private CheckBox cbSignature; private TextView tvReference; private ImageButton ibCloseRefHint; - private ImageButton ibReferenceDelete; private ImageButton ibReferenceEdit; private ImageButton ibReferenceImages; private BottomNavigationView style_bar; @@ -220,12 +217,11 @@ public class FragmentCompose extends FragmentBase { private static final int REQUEST_RECORD_AUDIO = 7; private static final int REQUEST_ENCRYPT = 8; private static final int REQUEST_COLOR = 9; - private static final int REQUEST_REF_DELETE = 10; - private static final int REQUEST_CONTACT_GROUP = 11; - private static final int REQUEST_ANSWER = 12; - private static final int REQUEST_LINK = 13; - private static final int REQUEST_DISCARD = 14; - private static final int REQUEST_SEND = 15; + private static final int REQUEST_CONTACT_GROUP = 10; + private static final int REQUEST_ANSWER = 11; + private static final int REQUEST_LINK = 12; + private static final int REQUEST_DISCARD = 13; + private static final int REQUEST_SEND = 14; @Override public void onCreate(Bundle savedInstanceState) { @@ -268,7 +264,6 @@ public class FragmentCompose extends FragmentBase { cbSignature = view.findViewById(R.id.cbSignature); tvReference = view.findViewById(R.id.tvReference); ibCloseRefHint = view.findViewById(R.id.ibCloseRefHint); - ibReferenceDelete = view.findViewById(R.id.ibReferenceDelete); ibReferenceEdit = view.findViewById(R.id.ibReferenceEdit); ibReferenceImages = view.findViewById(R.id.ibReferenceImages); style_bar = view.findViewById(R.id.style_bar); @@ -443,13 +438,6 @@ public class FragmentCompose extends FragmentBase { } }); - ibReferenceDelete.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - onReferenceDelete(); - } - }); - ibReferenceEdit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -547,7 +535,6 @@ public class FragmentCompose extends FragmentBase { grpBody.setVisibility(View.GONE); grpSignature.setVisibility(View.GONE); grpReferenceHint.setVisibility(View.GONE); - ibReferenceDelete.setVisibility(View.GONE); ibReferenceEdit.setVisibility(View.GONE); ibReferenceImages.setVisibility(View.GONE); tvReference.setVisibility(View.GONE); @@ -663,77 +650,12 @@ public class FragmentCompose extends FragmentBase { return view; } - private void onReferenceDelete() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - if (prefs.getBoolean("delete_ref_confirmed", false)) { - onReferenceDeleteConfirmed(); - return; - } - - Bundle args = new Bundle(); - args.putString("question", getString(R.string.title_ask_delete_ref)); - args.putString("notagain", "delete_ref_confirmed"); - - FragmentDialogAsk fragment = new FragmentDialogAsk(); - fragment.setArguments(args); - fragment.setTargetFragment(this, REQUEST_REF_DELETE); - fragment.show(getParentFragmentManager(), "compose:refdelete"); - } - - private void onReferenceDeleteConfirmed() { - Bundle args = new Bundle(); - args.putLong("id", working); - - new SimpleTask() { - @Override - protected void onPreExecute(Bundle args) { - ibReferenceDelete.setEnabled(false); - ibReferenceEdit.setEnabled(false); - } - - @Override - protected void onPostExecute(Bundle args) { - ibReferenceDelete.setEnabled(true); - ibReferenceEdit.setEnabled(true); - } - - @Override - protected EntityMessage onExecute(Context context, Bundle args) throws Throwable { - long id = args.getLong("id"); - - DB db = DB.getInstance(context); - EntityMessage draft = db.message().getMessage(id); - if (draft != null) { - File refFile = draft.getRefFile(context); - refFile.delete(); - } - - return draft; - } - - @Override - protected void onExecuted(Bundle args, EntityMessage draft) { - if (draft != null) { - tvReference.setVisibility(View.GONE); - grpReferenceHint.setVisibility(View.GONE); - ibReferenceDelete.setVisibility(View.GONE); - ibReferenceEdit.setVisibility(View.GONE); - ibReferenceImages.setVisibility(View.GONE); - } - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getParentFragmentManager(), ex); - } - }.execute(this, args, "compose:refdelete"); - } - private void onReferenceEdit() { PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(getContext(), getViewLifecycleOwner(), ibReferenceEdit); popupMenu.getMenu().add(Menu.NONE, R.string.title_edit_plain_text, 1, R.string.title_edit_plain_text); popupMenu.getMenu().add(Menu.NONE, R.string.title_edit_formatted_text, 2, R.string.title_edit_formatted_text); + popupMenu.getMenu().add(Menu.NONE, R.string.title_delete, 3, R.string.title_delete); popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override @@ -747,6 +669,10 @@ public class FragmentCompose extends FragmentBase { convertRef(false); return true; + case R.string.title_delete: + deleteRef(); + return true; + default: return false; } @@ -761,13 +687,11 @@ public class FragmentCompose extends FragmentBase { new SimpleTask() { @Override protected void onPreExecute(Bundle args) { - ibReferenceDelete.setEnabled(false); ibReferenceEdit.setEnabled(false); } @Override protected void onPostExecute(Bundle args) { - ibReferenceDelete.setEnabled(true); ibReferenceEdit.setEnabled(true); } @@ -777,16 +701,27 @@ public class FragmentCompose extends FragmentBase { boolean plain = args.getBoolean("plain"); String body = args.getString("body"); - String html; - File refFile = EntityMessage.getRefFile(context, id); - String ref = Helper.readText(refFile); + Document doc = JsoupEx.parse(Helper.readText(EntityMessage.getFile(context, id))); + Elements ref = doc.select("div[fairemail=reference]"); + ref.removeAttr("fairemail"); + + Document document = JsoupEx.parse(body); if (plain) { - String text = HtmlHelper.getText(ref); - html = "

" + text.replaceAll("\\r?\\n", "
") + "

"; - } else - html = HtmlHelper.sanitize(context, ref, true, false); + String text = HtmlHelper.getText(ref.outerHtml()); + Element p = document.createElement("p"); + p.html(text.replaceAll("\\r?\\n", "
")); + if (document.body() != null) + document.body().appendChild(p); + } else { + Document d = HtmlHelper.sanitize(context, ref.outerHtml(), true, false); + Element b = d.body(); + if (document.body() != null && b != null) { + b.tagName("div"); + document.body().appendChild(b); + } + } - return body + html; + return document.html(); } @Override @@ -803,6 +738,13 @@ public class FragmentCompose extends FragmentBase { } }.execute(FragmentCompose.this, args, "compose:convert"); } + + private void deleteRef() { + Bundle extras = new Bundle(); + extras.putString("html", HtmlHelper.toHtml(etBody.getText())); + extras.putBoolean("show", true); + onAction(R.id.action_save, extras); + } }); popupMenu.show(); @@ -1316,10 +1258,6 @@ public class FragmentCompose extends FragmentBase { if (resultCode == RESULT_OK && data != null) onPgp(data); break; - case REQUEST_REF_DELETE: - if (resultCode == RESULT_OK) - onReferenceDeleteConfirmed(); - break; case REQUEST_CONTACT_GROUP: if (resultCode == RESULT_OK && data != null) onContactGroupSelected(data.getBundleExtra("args")); @@ -2187,7 +2125,7 @@ public class FragmentCompose extends FragmentBase { EntityMessage ref = db.message().getMessage(reference); - String body = ""; + Document document = JsoupEx.parse(""); data.draft = new EntityMessage(); data.draft.msgid = EntityMessage.generateMessageId(); @@ -2224,15 +2162,26 @@ public class FragmentCompose extends FragmentBase { } data.draft.subject = args.getString("subject", ""); - body = args.getString("body", ""); - if (!TextUtils.isEmpty(body)) - body = HtmlHelper.sanitize(context, body, false, false); + String b = args.getString("body", ""); + if (!TextUtils.isEmpty(b)) { + Document d = HtmlHelper.sanitize(context, b, false, false); + Element e = d.body(); + if (e != null) { + e.tagName("div"); + document.body().appendChild(e); + } + } if (answer > 0) { EntityAnswer a = db.answer().getAnswer(answer); if (a != null) { data.draft.subject = a.name; - body = a.getText(null) + body; + Document d = JsoupEx.parse(a.getText(null)); + Element e = d.body(); + if (e != null) { + e.tagName("div"); + document.body().appendChild(e); + } } } } else { @@ -2300,20 +2249,31 @@ public class FragmentCompose extends FragmentBase { data.draft.subject = ref.subject; if (ref.content) { String html = Helper.readText(ref.getFile(context)); - body = HtmlHelper.sanitize(context, html, true, false); + Document d = HtmlHelper.sanitize(context, html, true, false); + Element e = d.body(); + if (e != null) { + e.tagName("div"); + document.body().appendChild(e); + } } } else if ("list".equals(action)) { data.draft.subject = ref.subject; } else if ("receipt".equals(action)) { data.draft.subject = context.getString(R.string.title_receipt_subject, subject); - Configuration configuration = new Configuration(context.getResources().getConfiguration()); - configuration.setLocale(new Locale("en")); - Resources res = context.createConfigurationContext(configuration).getResources(); + Element p = document.createElement("p"); + p.text(context.getString(R.string.title_receipt_text)); + document.body().appendChild(p); + + if (!Locale.getDefault().getLanguage().equals("en")) { + Configuration configuration = new Configuration(context.getResources().getConfiguration()); + configuration.setLocale(new Locale("en")); + Resources res = context.createConfigurationContext(configuration).getResources(); - body = "

" + context.getString(R.string.title_receipt_text) + "

"; - if (!Locale.getDefault().getLanguage().equals("en")) - body += "

" + res.getString(R.string.title_receipt_text) + "

"; + p = document.createElement("p"); + p.text(res.getString(R.string.title_receipt_text)); + document.body().appendChild(p); + } } else if ("participation".equals(action)) data.draft.subject = status + ": " + ref.subject; @@ -2324,8 +2284,93 @@ public class FragmentCompose extends FragmentBase { if (answer > 0) { EntityAnswer a = db.answer().getAnswer(answer); - if (a != null) - body = a.getText(data.draft.to) + body; + if (a != null) { + Document d = JsoupEx.parse(a.getText(data.draft.to)); + Element e = d.body(); + if (e != null) { + e.tagName("div"); + document.body().appendChild(e); + } + } + } + + if (ref.content && + !"editasnew".equals(action) && + !"list".equals(action) && + !"receipt".equals(action)) { + // Reply/forward + Element div = document.createElement("div"); + div.attr("fairemail", "reference"); + + // Build reply header + Element p = document.createElement("p"); + DateFormat DF = Helper.getDateTimeInstance(context); + boolean extended_reply = prefs.getBoolean("extended_reply", false); + if (extended_reply) { + if (ref.from != null && ref.from.length > 0) { + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_from) + " "); + p.appendChild(strong); + p.appendText(MessageHelper.formatAddresses(ref.from)); + p.appendElement("br"); + } + if (ref.to != null && ref.to.length > 0) { + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_to) + " "); + p.appendChild(strong); + p.appendText(MessageHelper.formatAddresses(ref.to)); + p.appendElement("br"); + } + if (ref.cc != null && ref.cc.length > 0) { + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_cc) + " "); + p.appendChild(strong); + p.appendText(MessageHelper.formatAddresses(ref.cc)); + p.appendElement("br"); + } + { + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_received) + " "); + p.appendChild(strong); + p.appendText(DF.format(ref.received)); + p.appendElement("br"); + } + { + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_subject) + " "); + p.appendChild(strong); + p.appendText(ref.subject == null ? "" : ref.subject); + p.appendElement("br"); + } + } else + p.text(DF.format(new Date(ref.received)) + " " + MessageHelper.formatAddresses(ref.from) + ":"); + + div.appendChild(p); + + // Get referenced message body + Document d = JsoupEx.parse(Helper.readText(ref.getFile(context))); + + // Remove signature separators + boolean usenet = prefs.getBoolean("usenet_signature", false); + if (usenet) + for (Element span : d.select("span")) + if (span.childNodeSize() == 2 && + span.childNode(0) instanceof TextNode && + "-- ".equals(span.wholeText()) && + "br".equals(span.childNode(1).nodeName())) + span.remove(); + + // Quote referenced message body + Element e = d.body(); + if (e != null) { + boolean quote_reply = prefs.getBoolean("quote_reply", true); + boolean quote = (quote_reply && ("reply".equals(action) || "reply_all".equals(action))); + + e.tagName(quote ? "blockquote" : "div"); + div.appendChild(e); + } + + document.body().appendChild(div); } } @@ -2442,12 +2487,15 @@ public class FragmentCompose extends FragmentBase { data.draft.revisions = 1; data.draft.id = db.message().insertMessage(data.draft); - Helper.writeText(data.draft.getFile(context), body); + + String html = document.html(); + Helper.writeText(data.draft.getFile(context), html); + Helper.writeText(data.draft.getFile(context, data.draft.revision), html); db.message().setMessageContent(data.draft.id, true, data.draft.plain_only, - HtmlHelper.getPreview(body), + HtmlHelper.getPreview(html), null); if ("participation".equals(action)) { @@ -2464,87 +2512,6 @@ public class FragmentCompose extends FragmentBase { ics.renameTo(attachment.getFile(context)); } - // Write reference text - if (ref != null && ref.content && - !"editasnew".equals(action) && - !"list".equals(action) && - !"receipt".equals(action)) { - String refText = Helper.readText(ref.getFile(context)); - - boolean usenet = prefs.getBoolean("usenet_signature", false); - if (usenet) { - Document rdoc = JsoupEx.parse(refText); - - List tbd = new ArrayList<>(); - - NodeTraversor.traverse(new NodeVisitor() { - boolean found = false; - - public void head(Node node, int depth) { - if (node instanceof TextNode && - "-- ".equals(((TextNode) node).getWholeText()) && - node.nextSibling() != null && - "br".equals(node.nextSibling().nodeName())) - found = true; - if (found) - tbd.add(node); - } - - public void tail(Node node, int depth) { - // Do nothing - } - }, rdoc); - - if (tbd.size() > 0) { - for (Node node : tbd) - node.remove(); - - refText = (rdoc.body() == null ? "" : rdoc.body().html()); - } - } - - // Build reply header - StringBuilder sb = new StringBuilder(); - DateFormat DF = Helper.getDateTimeInstance(context); - boolean extended_reply = prefs.getBoolean("extended_reply", false); - if (extended_reply) { - sb.append("

"); - if (ref.from != null && ref.from.length > 0) - sb.append("").append(context.getString(R.string.title_from)).append(" ") - .append(Html.escapeHtml(MessageHelper.formatAddresses(ref.from))) - .append("
\n"); - if (ref.to != null && ref.to.length > 0) - sb.append("").append(context.getString(R.string.title_to)).append(" ") - .append(Html.escapeHtml(MessageHelper.formatAddresses(ref.to))). - append("
\n"); - if (ref.cc != null && ref.cc.length > 0) - sb.append("").append(context.getString(R.string.title_cc)).append(" ") - .append(Html.escapeHtml(MessageHelper.formatAddresses(ref.cc))) - .append("
\n"); - sb.append("").append(context.getString(R.string.title_received)).append(" ") - .append(Html.escapeHtml(DF.format(ref.received))) - .append("
\n"); - sb.append("").append(context.getString(R.string.title_subject)).append(" ") - .append(Html.escapeHtml(ref.subject == null ? "" : ref.subject)); - sb.append("

\n"); - } else { - sb.append("

"); - sb.append(Html.escapeHtml(DF.format(new Date(ref.received)))).append(" "); - sb.append(Html.escapeHtml(MessageHelper.formatAddresses(ref.from))).append(":"); - sb.append("

\n"); - } - - boolean quote_reply = prefs.getBoolean("quote_reply", true); - boolean quote = (quote_reply && ("reply".equals(action) || "reply_all".equals(action))); - if (quote) - sb.append("
"); - sb.append(refText); - if (quote) - sb.append("
"); - - Helper.writeText(data.draft.getRefFile(context), sb.toString()); - } - if ("new".equals(action)) { ArrayList uris = args.getParcelableArrayList("attachments"); if (uris != null) @@ -2581,22 +2548,43 @@ public class FragmentCompose extends FragmentBase { } } - // Create initial revision - EntityRevision revision = new EntityRevision(); - revision.message = data.draft.id; - revision.sequence = data.draft.revision; - revision.reference = data.draft.getRefFile(context).exists(); - db.revision().insertRevision(revision); - Helper.writeText(data.draft.getFile(context, data.draft.revision), body); - if (data.draft.encrypt == null || !data.draft.encrypt) EntityOperation.queue(context, data.draft, EntityOperation.ADD); } else { if (data.draft.content) { + + if (data.draft.revision == null) { + data.draft.revision = 1; + data.draft.revisions = 1; + db.message().setMessageRevision(data.draft.id, data.draft.revision); + db.message().setMessageRevisions(data.draft.id, data.draft.revisions); + } + File file = data.draft.getFile(context); - String html = Helper.readText(file); - html = HtmlHelper.sanitize(context, html, true, false); + + Document doc = JsoupEx.parse(Helper.readText(file)); + Elements ref = doc.select("div[fairemail=reference]"); + ref.remove(); + + File refFile = data.draft.getRefFile(context); + if (refFile.exists()) { + ref.html(Helper.readText(refFile)); + refFile.delete(); + } + + Document document = HtmlHelper.sanitize(context, doc.html(), true, false); + for (Element e : ref) + document.appendChild(e); + + String html = JsoupEx.parse(document.html()).html(); Helper.writeText(file, html); + Helper.writeText(data.draft.getFile(context, data.draft.revision), html); + + db.message().setMessageContent(data.draft.id, + true, + data.draft.plain_only, + HtmlHelper.getPreview(html), + null); } else { if (data.draft.uid == null) throw new IllegalStateException("Draft without uid"); @@ -2653,10 +2641,8 @@ public class FragmentCompose extends FragmentBase { grpAddresses.setVisibility("reply_all".equals(action) ? View.VISIBLE : View.GONE); ibCcBcc.setVisibility(View.VISIBLE); - bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible( - data.draft.revision != null && data.draft.revision > 1); - bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible( - data.draft.revision != null && !data.draft.revision.equals(data.draft.revisions)); + bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(data.draft.revision > 1); + bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(data.draft.revision < data.draft.revisions); if (args.getBoolean("incomplete")) Snackbar.make(view, R.string.title_attachments_incomplete, Snackbar.LENGTH_LONG).show(); @@ -2956,59 +2942,54 @@ public class FragmentCompose extends FragmentBase { db.message().updateMessage(draft); } - if (action == R.id.action_undo || action == R.id.action_redo) { - if (draft.revision != null && draft.revisions != null) { - Helper.writeText(draft.getFile(context, draft.revision), body); - - if (action == R.id.action_undo) { - if (draft.revision > 1) - draft.revision--; - } else { - if (draft.revision < draft.revisions) - draft.revision++; - } - - // Restore revision - Log.i("Restoring revision=" + draft.revision); - body = Helper.readText(draft.getFile(context, draft.revision)); + String p = Helper.readText(draft.getFile(context)); + Document doc = JsoupEx.parse(p); + if ((body != null && !body.equals(doc.html())) || + (extras != null && extras.containsKey("html"))) { + dirty = true; + + Elements ref = doc.select("div[fairemail=reference]"); + ref.remove(); + + // Get saved body + Document d; + if (extras != null && extras.containsKey("html")) { + // Save current revision + Document c = JsoupEx.parse(body); + if (c.body() != null && ref.size() > 0) + c.body().appendChild(ref.first()); + Helper.writeText(draft.getFile(context, draft.revision), c.html()); + + d = JsoupEx.parse(extras.getString("html")); + } else { + d = JsoupEx.parse(body); + if (d.body() != null && ref.size() > 0) + d.body().appendChild(ref.first()); } - } else { - String previous = Helper.readText(draft.getFile(context)); - if ((body != null && !body.equals(previous)) || - (extras != null && extras.containsKey("html"))) { - dirty = true; - - // Get revision info - boolean reference; - if (extras != null && extras.containsKey("html")) { - reference = false; - body = extras.getString("html"); - } else { - File refFile = EntityMessage.getRefFile(context, draft.id); - if (draft.revision == null) - reference = refFile.exists(); - else { - EntityRevision revision = db.revision().getRevision(draft.id, draft.revision); - reference = (revision == null ? refFile.exists() : revision.reference); - } - } - // Create new revision - if (draft.revisions == null) - draft.revisions = 1; - else - draft.revisions++; + body = d.html(); + + // Create new revision + if (action != R.id.action_undo && action != R.id.action_redo) { + draft.revisions++; draft.revision = draft.revisions; + } - Log.i("Creating revision sequence=" + draft.revision + " reference=" + reference); - EntityRevision revision = new EntityRevision(); - revision.message = draft.id; - revision.sequence = draft.revision; - revision.reference = reference; - db.revision().insertRevision(revision); + Helper.writeText(draft.getFile(context, draft.revision), body); + } - Helper.writeText(draft.getFile(context, draft.revision), body); + if (action == R.id.action_undo || action == R.id.action_redo) { + if (action == R.id.action_undo) { + if (draft.revision > 1) + draft.revision--; + } else { + if (draft.revision < draft.revisions) + draft.revision++; } + + // Restore revision + Log.i("Restoring revision=" + draft.revision); + body = Helper.readText(draft.getFile(context, draft.revision)); } Helper.writeText(draft.getFile(context), body); @@ -3094,13 +3075,8 @@ public class FragmentCompose extends FragmentBase { } else if (action == R.id.action_send) { // Remove unused inline images - StringBuilder sb = new StringBuilder(); - sb.append(body); - File rfile = draft.getRefFile(context); - if (rfile.exists()) - sb.append(Helper.readText(rfile)); List cids = new ArrayList<>(); - for (Element element : JsoupEx.parse(sb.toString()).select("img")) { + for (Element element : JsoupEx.parse(body).select("img")) { String src = element.attr("src"); if (src.startsWith("cid:")) cids.add("<" + src.substring(4) + ">"); @@ -3115,8 +3091,6 @@ public class FragmentCompose extends FragmentBase { // Delete draft (cannot move to outbox) EntityOperation.queue(context, draft, EntityOperation.DELETE); - File refDraftFile = draft.getRefFile(context); - // Copy message to outbox draft.id = null; draft.folder = db.folder().getOutbox().id; @@ -3124,10 +3098,6 @@ public class FragmentCompose extends FragmentBase { draft.ui_hide = false; draft.id = db.message().insertMessage(draft); Helper.writeText(draft.getFile(context), body); - if (refDraftFile.exists()) { - File refFile = draft.getRefFile(context); - refDraftFile.renameTo(refFile); - } // Move attachments for (EntityAttachment attachment : attachments) @@ -3183,8 +3153,8 @@ public class FragmentCompose extends FragmentBase { etCc.setText(MessageHelper.formatAddressesCompose(draft.cc)); etBcc.setText(MessageHelper.formatAddressesCompose(draft.bcc)); - bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision != null && draft.revision > 1); - bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision != null && !draft.revision.equals(draft.revisions)); + bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision > 1); + bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision < draft.revisions); if (action == R.id.action_delete) { autosave = false; @@ -3289,8 +3259,8 @@ public class FragmentCompose extends FragmentBase { pbWait.setVisibility(View.GONE); media_bar.setVisibility(media ? View.VISIBLE : View.GONE); - bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision != null && draft.revision > 1); - bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision != null && !draft.revision.equals(draft.revisions)); + bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision > 1); + bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision < draft.revisions); bottom_navigation.setVisibility(View.VISIBLE); Helper.setViewsEnabled(view, true); @@ -3310,8 +3280,11 @@ public class FragmentCompose extends FragmentBase { if (draft == null || !draft.content) throw new IllegalArgumentException(context.getString(R.string.title_no_body)); - String body = Helper.readText(draft.getFile(context)); - Spanned spannedBody = HtmlHelper.fromHtml(body, new Html.ImageGetter() { + Document doc = JsoupEx.parse(Helper.readText(draft.getFile(context))); + Elements ref = doc.select("div[fairemail=reference]"); + ref.remove(); + + Spanned spannedBody = HtmlHelper.fromHtml(doc.html(), new Html.ImageGetter() { @Override public Drawable getDrawable(String source) { return ImageHelper.decodeImage(context, id, source, true, zoom, etBody); @@ -3332,13 +3305,9 @@ public class FragmentCompose extends FragmentBase { spannedBody = bodyBuilder; Spanned spannedRef = null; - File refFile = draft.getRefFile(context); - EntityRevision revision = db.revision().getRevision(draft.id, draft.revision == null ? 0 : draft.revision); - Log.i("Viewing revision=" + (revision == null ? null : revision.sequence) + - " reference=" + (revision == null ? null : revision.reference)); - if (revision == null ? refFile.exists() : revision.reference) { - String quote = HtmlHelper.sanitize(context, Helper.readText(refFile), show_images, false); - Spanned spannedQuote = HtmlHelper.fromHtml(quote, + if (!ref.isEmpty()) { + Document quote = HtmlHelper.sanitize(context, ref.outerHtml(), show_images, false); + Spanned spannedQuote = HtmlHelper.fromHtml(quote.html(), new Html.ImageGetter() { @Override public Drawable getDrawable(String source) { @@ -3384,7 +3353,6 @@ public class FragmentCompose extends FragmentBase { tvReference.setText(text[1]); tvReference.setVisibility(text[1] == null ? View.GONE : View.VISIBLE); grpReferenceHint.setVisibility(text[1] == null || !ref_hint ? View.GONE : View.VISIBLE); - ibReferenceDelete.setVisibility(text[1] == null ? View.GONE : View.VISIBLE); ibReferenceEdit.setVisibility(text[1] == null ? View.GONE : View.VISIBLE); ibReferenceImages.setVisibility(ref_has_images && !show_images ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java index 2bd2e237e9..4d61b95fbe 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java @@ -69,7 +69,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc private final static String[] RESET_QUESTIONS = new String[]{ "welcome", "crash_reports_asked", "html_always_images", "print_html_confirmed", - "identities_asked", "delete_ref_confirmed", "send_dialog" + "identities_asked", "compose_reference", "send_dialog" }; @Override diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index d81d9b3c21..a1566a83b1 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -80,17 +80,21 @@ public class HtmlHelper { private static final List tails = Collections.unmodifiableList(Arrays.asList( "h1", "h2", "h3", "h4", "h5", "h6", "p", "ol", "ul", "li")); - static String sanitize(Context context, String html, boolean show_images, boolean autolink) { + static Document sanitize(Context context, String html, boolean show_images, boolean autolink) { try { return _sanitize(context, html, show_images, autolink); } catch (Throwable ex) { // OutOfMemoryError Log.e(ex); - return Helper.formatThrowable(ex); + Document document = new Document(""); + Element strong = document.createElement("strong"); + strong.text(Helper.formatThrowable(ex)); + document.appendChild(strong); + return document; } } - private static String _sanitize(Context context, String html, boolean show_images, boolean autolink) { + private static Document _sanitize(Context context, String html, boolean show_images, boolean autolink) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean text_color = prefs.getBoolean("text_color", true); boolean display_hidden = prefs.getBoolean("display_hidden", false); @@ -472,8 +476,7 @@ public class HtmlHelper { if (!TextUtils.isEmpty(span.attr("color"))) span.tagName("font"); - Element body = document.body(); - return (body == null ? "" : body.html()); + return document; } private static boolean hasVisibleContent(List nodes) { diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index fe27867b6a..12af298034 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -31,6 +31,8 @@ import com.sun.mail.util.FolderClosedIOException; import com.sun.mail.util.MessageRemovedIOException; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import java.io.BufferedReader; import java.io.File; @@ -284,9 +286,6 @@ public class MessageHelper { } static void build(Context context, EntityMessage message, List attachments, EntityIdentity identity, MimeMessage imessage) throws IOException, MessagingException { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean usenet = prefs.getBoolean("usenet_signature", false); - if (message.receipt != null && message.receipt) { // https://www.ietf.org/rfc/rfc3798.txt Multipart report = new MimeMultipart("report; report-type=disposition-notification"); @@ -322,31 +321,30 @@ public class MessageHelper { } // Build html body - StringBuilder body = new StringBuilder(); - body.append(""); - - Document mdoc = JsoupEx.parse(Helper.readText(message.getFile(context))); - if (mdoc.body() != null) - body.append(mdoc.body().html()); - - // When sending message - if (identity != null) { - if (!TextUtils.isEmpty(identity.signature) && message.signature) { - Document sdoc = JsoupEx.parse(identity.signature); - if (sdoc.body() != null) { - if (usenet) // https://www.ietf.org/rfc/rfc3676.txt - body.append("--
"); - body.append(sdoc.body().html()); + Document document = JsoupEx.parse(Helper.readText(message.getFile(context))); + Elements ref = document.select("div[fairemail=reference]"); + ref.remove(); + ref.removeAttr("fairemail"); + + if (document.body() != null) { + // When sending message + if (identity != null && !TextUtils.isEmpty(identity.signature) && message.signature) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean usenet = prefs.getBoolean("usenet_signature", false); + if (usenet) { + // https://www.ietf.org/rfc/rfc3676.txt + Element span = document.createElement("span"); + span.text("-- "); + span.appendElement("br"); + document.body().appendChild(span); } + document.body().append(identity.signature); } - File refFile = message.getRefFile(context); - if (refFile.exists()) - body.append(Helper.readText(refFile)); + if (ref.size() > 0) + document.body().appendChild(ref.first()); } - body.append(""); - // multipart/mixed // multipart/related // multipart/alternative @@ -355,7 +353,7 @@ public class MessageHelper { // inlines // attachments - String htmlContent = body.toString(); + String htmlContent = document.html(); String plainContent = HtmlHelper.getText(htmlContent); BodyPart plainPart = new MimeBodyPart(); diff --git a/app/src/main/res/layout/fragment_compose.xml b/app/src/main/res/layout/fragment_compose.xml index 2826e87e5d..a4cb420b24 100644 --- a/app/src/main/res/layout/fragment_compose.xml +++ b/app/src/main/res/layout/fragment_compose.xml @@ -339,23 +339,10 @@ android:text="@string/title_no_format" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textStyle="italic" - app:layout_constraintEnd_toStartOf="@+id/ibReferenceDelete" + app:layout_constraintEnd_toStartOf="@+id/ibReferenceEdit" app:layout_constraintStart_toEndOf="@id/ibCloseRefHint" app:layout_constraintTop_toBottomOf="@id/vSeparatorSignature" /> - - + app:constraint_referenced_ids="ibCloseRefHint,tvReferenceHint,ibReferenceEdit,ibReferenceImages" /> Always show images on showing original messages
Showing images can leak privacy sensitive information Images recognized as tracking images will not be shown - Delete replied/forwarded message text? This cannot be undone. Synchronize all messages in %1$s? Delete local messages? Messages will remain on the remote server. Help improve FairEmail