diff --git a/app/schemas/eu.faircode.email.DB/4.json b/app/schemas/eu.faircode.email.DB/4.json new file mode 100644 index 0000000000..6cf3b2ca38 --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/4.json @@ -0,0 +1,1092 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "b13e5993b2a654ad20a5faca482a014e", + "entities": [ + { + "tableName": "identity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` 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, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `color` INTEGER, `signature` TEXT, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `sent_folder` 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": "replyto", + "columnName": "replyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "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": "auth_type", + "columnName": "auth_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "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": "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, `name` TEXT, `signature` TEXT, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `color` INTEGER, `poll_interval` INTEGER NOT NULL, `created` INTEGER, `state` TEXT, `error` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "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": "user", + "columnName": "user", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "auth_type", + "columnName": "auth_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "poll_interval", + "columnName": "poll_interval", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "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": [], + "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, `synchronize` INTEGER NOT NULL, `sync_days` INTEGER NOT NULL, `keep_days` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `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": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "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": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "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, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `size` INTEGER, `content` INTEGER NOT NULL, `preview` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `ui_ignored` INTEGER NOT NULL, `error` TEXT, 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": "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": "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": "flagged", + "columnName": "flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_seen", + "columnName": "ui_seen", + "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": "error", + "columnName": "error", + "affinity": "TEXT", + "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_ui_found", + "unique": true, + "columnNames": [ + "folder", + "uid", + "ui_found" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid_ui_found` ON `${TABLE_NAME}` (`folder`, `uid`, `ui_found`)" + }, + { + "name": "index_message_msgid_folder_ui_found", + "unique": true, + "columnNames": [ + "msgid", + "folder", + "ui_found" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder_ui_found` ON `${TABLE_NAME}` (`msgid`, `folder`, `ui_found`)" + }, + { + "name": "index_message_thread", + "unique": false, + "columnNames": [ + "thread" + ], + "createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)" + }, + { + "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_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`)" + } + ], + "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, `cid` TEXT, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, 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": "cid", + "columnName": "cid", + "affinity": "TEXT", + "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 + } + ], + "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 NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, 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": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "args", + "columnName": "args", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": true + } + ], + "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": "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, \"b13e5993b2a654ad20a5faca482a014e\")" + ] + } +} \ 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 defa71b22d..d733e91954 100644 --- a/app/src/main/java/eu/faircode/email/DB.java +++ b/app/src/main/java/eu/faircode/email/DB.java @@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory; // https://developer.android.com/topic/libraries/architecture/room.html @Database( - version = 3, + version = 4, entities = { EntityIdentity.class, EntityAccount.class, @@ -138,6 +138,15 @@ public abstract class DB extends RoomDatabase { " (SELECT account.signature FROM account WHERE account.id = identity.account)"); } }) + .addMigrations(new Migration(3, 4) { + @Override + public void migrate(SupportSQLiteDatabase db) { + Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion); + db.execSQL("ALTER TABLE `message` ADD COLUMN `forwarding` INTEGER" + + " REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL"); + db.execSQL("CREATE INDEX `index_message_forwarding` ON `message` (`forwarding`)"); + } + }) .build(); } diff --git a/app/src/main/java/eu/faircode/email/EntityMessage.java b/app/src/main/java/eu/faircode/email/EntityMessage.java index a02318c6e8..0e56c0456e 100644 --- a/app/src/main/java/eu/faircode/email/EntityMessage.java +++ b/app/src/main/java/eu/faircode/email/EntityMessage.java @@ -20,6 +20,7 @@ package eu.faircode.email; */ import android.content.Context; +import android.text.Html; import android.util.Log; import java.io.BufferedReader; @@ -51,13 +52,15 @@ import static androidx.room.ForeignKey.SET_NULL; @ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE), @ForeignKey(childColumns = "folder", entity = EntityFolder.class, parentColumns = "id", onDelete = CASCADE), @ForeignKey(childColumns = "identity", entity = EntityIdentity.class, parentColumns = "id", onDelete = SET_NULL), - @ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL) + @ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL), + @ForeignKey(childColumns = "forwarding", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL) }, indices = { @Index(value = {"account"}), @Index(value = {"folder"}), @Index(value = {"identity"}), @Index(value = {"replying"}), + @Index(value = {"forwarding"}), @Index(value = {"folder", "uid", "ui_found"}, unique = true), @Index(value = {"msgid", "folder", "ui_found"}, unique = true), @Index(value = {"thread"}), @@ -80,6 +83,7 @@ public class EntityMessage implements Serializable { public Long identity; public String extra; // plus public Long replying; + public Long forwarding; public Long uid; // compose = null public String msgid; public String references; @@ -178,6 +182,14 @@ public class EntityMessage implements Serializable { } } + static String getQuote(Context context, long id) throws IOException { + EntityMessage message = DB.getInstance(context).message().getMessage(id); + return String.format("

%s %s:

%s
", + Html.escapeHtml(new Date(message.sent == null ? message.received : message.sent).toString()), + Html.escapeHtml(MessageHelper.getFormattedAddresses(message.from, true)), + HtmlHelper.sanitize(EntityMessage.read(context, id))); + } + @Override public boolean equals(Object obj) { if (obj instanceof EntityMessage) { @@ -186,6 +198,7 @@ public class EntityMessage implements Serializable { this.folder.equals(other.folder) && (this.identity == null ? other.identity == null : this.identity.equals(other.identity)) && (this.replying == null ? other.replying == null : this.replying.equals(other.replying)) && + (this.forwarding == null ? other.forwarding == null : this.replying.equals(other.forwarding)) && (this.uid == null ? other.uid == null : this.uid.equals(other.uid)) && (this.msgid == null ? other.msgid == null : this.msgid.equals(other.msgid)) && (this.references == null ? other.references == null : this.references.equals(other.references)) && diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index bd88bef7ad..daa3e751e1 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -128,6 +128,8 @@ public class FragmentCompose extends FragmentEx { private EditText etSubject; private RecyclerView rvAttachment; private EditText etBody; + private TextView tvSignature; + private TextView tvReference; private BottomNavigationView edit_bar; private BottomNavigationView bottom_navigation; private ProgressBar pbWait; @@ -135,14 +137,23 @@ public class FragmentCompose extends FragmentEx { private Group grpExtra; private Group grpAddresses; private Group grpAttachments; + private Group grpSignature; + private Group grpReference; private AdapterAttachment adapter; private long working = -1; private boolean autosave = false; + private boolean pro = false; private OpenPgpServiceConnection pgpService; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + pro = Helper.isPro(getContext()); + } + @Override @Nullable public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -164,6 +175,8 @@ public class FragmentCompose extends FragmentEx { etSubject = view.findViewById(R.id.etSubject); rvAttachment = view.findViewById(R.id.rvAttachment); etBody = view.findViewById(R.id.etBody); + tvSignature = view.findViewById(R.id.tvSignature); + tvReference = view.findViewById(R.id.tvReference); edit_bar = view.findViewById(R.id.edit_bar); bottom_navigation = view.findViewById(R.id.bottom_navigation); pbWait = view.findViewById(R.id.pbWait); @@ -171,6 +184,8 @@ public class FragmentCompose extends FragmentEx { grpExtra = view.findViewById(R.id.grpExtra); grpAddresses = view.findViewById(R.id.grpAddresses); grpAttachments = view.findViewById(R.id.grpAttachments); + grpSignature = view.findViewById(R.id.grpSignature); + grpReference = view.findViewById(R.id.grpReference); // Wire controls spIdentity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @@ -180,17 +195,9 @@ public class FragmentCompose extends FragmentEx { int at = (identity == null ? -1 : identity.email.indexOf('@')); tvExtraPrefix.setText(at < 0 ? null : identity.email.substring(0, at) + "+"); tvExtraSuffix.setText(at < 0 ? null : identity.email.substring(at)); - - String signature = (identity == null ? null : identity.signature); - if (TextUtils.isEmpty(signature)) - signature = "‌"; - - String html = Html.toHtml(etBody.getText()); - int cstart = html.indexOf(""); - int cend = html.lastIndexOf(""); - if (cstart >= 0 && cend > cstart) { - html = html.substring(0, cstart + 4) + signature + html.substring(cend); - etBody.setText(Html.fromHtml(html)); + if (pro) { + tvSignature.setText(identity == null ? null : Html.fromHtml(identity.signature)); + grpSignature.setVisibility(identity == null || TextUtils.isEmpty(identity.signature) ? View.GONE : View.VISIBLE); } } @@ -198,6 +205,8 @@ public class FragmentCompose extends FragmentEx { public void onNothingSelected(AdapterView parent) { tvExtraPrefix.setText(null); tvExtraSuffix.setText(null); + tvSignature.setText(null); + grpSignature.setVisibility(View.GONE); } }); @@ -298,6 +307,8 @@ public class FragmentCompose extends FragmentEx { grpAddresses.setVisibility(View.GONE); grpAttachments.setVisibility(View.GONE); etBody.setVisibility(View.GONE); + grpSignature.setVisibility(View.GONE); + grpReference.setVisibility(View.GONE); edit_bar.setVisibility(View.GONE); bottom_navigation.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); @@ -640,7 +651,7 @@ public class FragmentCompose extends FragmentEx { Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, false); Session isession = Session.getInstance(props, null); MimeMessage imessage = new MimeMessage(isession); - MessageHelper.build(context, message, attachments, imessage); + MessageHelper.build(context, message, imessage); // Serialize message ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -918,6 +929,7 @@ public class FragmentCompose extends FragmentEx { UnderlineSpan[] uspans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class); for (UnderlineSpan uspan : uspans) spannable.removeSpan(uspan); + args.putString("body", Html.toHtml(spannable)); Log.i(Helper.TAG, "Run load id=" + working); @@ -1026,7 +1038,6 @@ public class FragmentCompose extends FragmentEx { long reference = args.getLong("reference", -1); boolean raw = args.getBoolean("raw", false); long answer = args.getLong("answer", -1); - boolean pro = Helper.isPro(getContext()); Log.i(Helper.TAG, "Load draft action=" + action + " id=" + id + " reference=" + reference); @@ -1140,9 +1151,6 @@ public class FragmentCompose extends FragmentEx { body = ""; else body = body.replaceAll("\\r?\\n", "
"); - - if (pro) - body += "

"; } else { result.draft.thread = ref.thread; @@ -1170,28 +1178,14 @@ public class FragmentCompose extends FragmentEx { } } else if ("forward".equals(action)) { - //msg.replying = ref.id; + result.draft.forwarding = ref.id; result.draft.from = ref.to; } - long time = (ref.sent == null ? ref.received : ref.sent); - - if ("reply".equals(action) || "reply_all".equals(action)) { + if ("reply".equals(action) || "reply_all".equals(action)) result.draft.subject = context.getString(R.string.title_subject_reply, ref.subject); - body = String.format("

%s %s:

%s
", - Html.escapeHtml(new Date(time).toString()), - Html.escapeHtml(MessageHelper.getFormattedAddresses(result.draft.to, true)), - HtmlHelper.sanitize(ref.read(context))); - } else if ("forward".equals(action)) { + else if ("forward".equals(action)) result.draft.subject = context.getString(R.string.title_subject_forward, ref.subject); - body = String.format("

%s %s:

%s
", - Html.escapeHtml(new Date(time).toString()), - Html.escapeHtml(MessageHelper.getFormattedAddresses(ref.from, true)), - HtmlHelper.sanitize(ref.read(context))); - } - - if (pro) - body = "

" + body; if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action))) { String text = db.answer().getAnswer(answer).text; @@ -1206,8 +1200,7 @@ public class FragmentCompose extends FragmentEx { text = text.replace("$email$", email == null ? "" : email); body = text + body; - } else - body = "

" + body; + } } result.draft.content = true; @@ -1302,22 +1295,36 @@ public class FragmentCompose extends FragmentEx { etBody.setText(null); - Bundle a = new Bundle(); + final Bundle a = new Bundle(); a.putLong("id", result.draft.id); + if (result.draft.replying != null) + a.putLong("reference", result.draft.replying); + else if (result.draft.forwarding != null) + a.putLong("reference", result.draft.forwarding); - new SimpleTask() { + new SimpleTask() { @Override - protected Spanned onLoad(final Context context, Bundle args) throws Throwable { - final long id = args.getLong("id"); + protected Spanned[] onLoad(final Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); + long reference = args.getLong("reference", -1); + String body = EntityMessage.read(context, id); - return Html.fromHtml(body, cidGetter, null); + String quote = (reference < 0 ? null : EntityMessage.getQuote(context, reference)); + + return new Spanned[]{ + Html.fromHtml(body, cidGetter, null), + quote == null ? null : Html.fromHtml(quote)}; } @Override - protected void onLoaded(Bundle args, Spanned body) { + protected void onLoaded(Bundle args, Spanned[] texts) { getActivity().invalidateOptionsMenu(); - etBody.setText(body); + etBody.setText(texts[0]); etBody.setSelection(0); + + tvReference.setText(texts[1]); + grpReference.setVisibility(texts[1] == null ? View.GONE : View.VISIBLE); + new Handler().post(new Runnable() { @Override public void run() { diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index d58a8fd512..0da03a7a80 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -173,14 +173,19 @@ public class MessageHelper { return props; } - static MimeMessageEx from(Context context, EntityMessage message, EntityMessage reply, List attachments, Session isession) throws MessagingException, IOException { + static MimeMessageEx from(Context context, EntityMessage message, Session isession) throws MessagingException, IOException { + DB db = DB.getInstance(context); MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid); - if (reply == null) + EntityMessage replying = null; + if (message.replying != null) + replying = db.message().getMessage(message.replying); + + if (replying == null) imessage.addHeader("References", message.msgid); else { - imessage.addHeader("In-Reply-To", reply.msgid); - imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid); + imessage.addHeader("In-Reply-To", replying.msgid); + imessage.addHeader("References", (replying.references == null ? "" : replying.references + " ") + replying.msgid); } imessage.setFlag(Flags.Flag.SEEN, message.seen); @@ -210,6 +215,8 @@ public class MessageHelper { imessage.setSentDate(new Date()); + List attachments = db.attachment().getAttachments(message.id); + if (message.from != null && message.from.length > 0) for (EntityAttachment attachment : attachments) if (attachment.available && "signature.asc".equals(attachment.name)) { @@ -265,14 +272,25 @@ public class MessageHelper { return imessage; } - build(context, message, attachments, imessage); + build(context, message, imessage); return imessage; } - static void build(Context context, EntityMessage message, List attachments, MimeMessage imessage) throws IOException, MessagingException { + static void build(Context context, EntityMessage message, MimeMessage imessage) throws IOException, MessagingException { + DB db = DB.getInstance(context); + String body = message.read(context); + if (Helper.isPro(context) && message.identity != null) { + EntityIdentity identity = db.identity().getIdentity(message.identity); + if (!TextUtils.isEmpty(identity.signature)) + body += identity.signature; + } + + if (message.replying != null || message.forwarding != null) + body += EntityMessage.getQuote(context, message.replying == null ? message.forwarding : message.replying); + BodyPart plain = new MimeBodyPart(); plain.setContent(Jsoup.parse(body).text(), "text/plain; charset=" + Charset.defaultCharset().name()); @@ -283,6 +301,7 @@ public class MessageHelper { alternative.addBodyPart(plain); alternative.addBodyPart(html); + List attachments = db.attachment().getAttachments(message.id); if (attachments.size() == 0) { imessage.setContent(alternative); } else { diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index d8af8875e5..7d803c50a8 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -1398,8 +1398,7 @@ public class ServiceSynchronize extends LifecycleService { private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException { // Append message - List attachments = db.attachment().getAttachments(message.id); - MimeMessage imessage = MessageHelper.from(this, message, null, attachments, isession); + MimeMessage imessage = MessageHelper.from(this, message, isession); AppendUID[] uid = ifolder.appendUIDMessages(new Message[]{imessage}); db.message().setMessageUid(message.id, uid[0].uid); Log.i(Helper.TAG, "Appended uid=" + uid[0].uid); @@ -1432,14 +1431,12 @@ public class ServiceSynchronize extends LifecycleService { } else { Log.w(Helper.TAG, "MOVE by DELETE/APPEND"); - List attachments = db.attachment().getAttachments(message.id); - if (!EntityFolder.ARCHIVE.equals(folder.type)) { imessage.setFlag(Flags.Flag.DELETED, true); ifolder.expunge(); } - MimeMessageEx icopy = MessageHelper.from(this, message, null, attachments, isession); + MimeMessageEx icopy = MessageHelper.from(this, message, isession); Folder itarget = istore.getFolder(target.name); itarget.appendMessages(new Message[]{icopy}); } @@ -1470,10 +1467,7 @@ public class ServiceSynchronize extends LifecycleService { final Session isession = Session.getInstance(props, null); // Create message - MimeMessage imessage; - EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying)); - List attachments = db.attachment().getAttachments(message.id); - imessage = MessageHelper.from(this, message, reply, attachments, isession); + MimeMessage imessage = MessageHelper.from(this, message, isession); if (ident.replyto != null) imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)}); diff --git a/app/src/main/res/layout/fragment_compose.xml b/app/src/main/res/layout/fragment_compose.xml index 62ff32e766..9040f846b6 100644 --- a/app/src/main/res/layout/fragment_compose.xml +++ b/app/src/main/res/layout/fragment_compose.xml @@ -205,11 +205,51 @@ android:gravity="top" android:hint="@string/title_body_hint" android:inputType="textCapSentences|textMultiLine" - android:minHeight="480dp" + android:minHeight="90dp" android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/vSeparator" /> + + + + + + + + + + + +