diff --git a/FAQ.md b/FAQ.md index 9873da15a3..ef9822b04a 100644 --- a/FAQ.md +++ b/FAQ.md @@ -323,18 +323,19 @@ If you are using a VPN, the VPN provider might block the connection because it i **(23) Why do I get 'Too many simultaneous connections' ?** -The message *Too many simultaneous connections* is sent by the email server when there are too many connections to the same email account at the same time. +The message *Too many simultaneous connections* is sent by the email server +when there are too many folder connections for the same email account at the same time. Possible causes are: * There are multiple email clients connected to the same account * The same email client is connected multiple times to the same account -* The previous connection was terminated abruptly for example by losing internet connectivity +* The previous connection was terminated abruptly for example by abruptly losing internet connectivity, for example when turning on flight mode If only FairEmail is connecting to the email server, first try to wait half an hour to see if the problem resolves itself, -else try to reduce the number of folders to synchronize. +else enable the folder settings '*Poll instead of synchronize*' for some folders. -The maximum number of simultaneous connections for Gmail is 15, +The maximum number of simultaneous folder connections for Gmail is 15, so you can synchronize at most 15 folders simultaneously on *all* your devices at the same time. See [here](https://support.google.com/mail/answer/7126229) for details. diff --git a/app/schemas/eu.faircode.email.DB/16.json b/app/schemas/eu.faircode.email.DB/16.json new file mode 100644 index 0000000000..6e01fc8fa4 --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/16.json @@ -0,0 +1,1180 @@ +{ + "formatVersion": 1, + "database": { + "version": 16, + "identityHash": "343e402af5a0888b893278879baad58d", + "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, `notify` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `created` INTEGER, `state` TEXT, `error` TEXT, `last_connected` INTEGER)", + "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": "notify", + "columnName": "notify", + "affinity": "INTEGER", + "notNull": true + }, + { + "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 + }, + { + "fieldPath": "last_connected", + "columnName": "last_connected", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "folder", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `level` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `poll` INTEGER NOT NULL, `sync_days` INTEGER NOT NULL, `keep_days` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `keywords` TEXT, `state` TEXT, `sync_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": "level", + "columnName": "level", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "poll", + "columnName": "poll", + "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": "keywords", + "columnName": "keywords", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sync_state", + "columnName": "sync_state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "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, `answered` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `keywords` TEXT, `ui_seen` INTEGER NOT NULL, `ui_answered` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `ui_ignored` INTEGER NOT NULL, `ui_browsed` INTEGER NOT NULL, `error` TEXT, `last_attempt` INTEGER, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`forwarding`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folder", + "columnName": "folder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identity", + "columnName": "identity", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "extra", + "columnName": "extra", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "replying", + "columnName": "replying", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forwarding", + "columnName": "forwarding", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "msgid", + "columnName": "msgid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "references", + "columnName": "references", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deliveredto", + "columnName": "deliveredto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inreplyto", + "columnName": "inreplyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thread", + "columnName": "thread", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "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": "answered", + "columnName": "answered", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "flagged", + "columnName": "flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keywords", + "columnName": "keywords", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ui_seen", + "columnName": "ui_seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_answered", + "columnName": "ui_answered", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_flagged", + "columnName": "ui_flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_hide", + "columnName": "ui_hide", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_found", + "columnName": "ui_found", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_ignored", + "columnName": "ui_ignored", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_browsed", + "columnName": "ui_browsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "last_attempt", + "columnName": "last_attempt", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_message_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_message_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_message_identity", + "unique": false, + "columnNames": [ + "identity" + ], + "createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)" + }, + { + "name": "index_message_replying", + "unique": false, + "columnNames": [ + "replying" + ], + "createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)" + }, + { + "name": "index_message_forwarding", + "unique": false, + "columnNames": [ + "forwarding" + ], + "createSql": "CREATE INDEX `index_message_forwarding` ON `${TABLE_NAME}` (`forwarding`)" + }, + { + "name": "index_message_folder_uid_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_flagged", + "unique": false, + "columnNames": [ + "ui_flagged" + ], + "createSql": "CREATE INDEX `index_message_ui_flagged` ON `${TABLE_NAME}` (`ui_flagged`)" + }, + { + "name": "index_message_ui_hide", + "unique": false, + "columnNames": [ + "ui_hide" + ], + "createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)" + }, + { + "name": "index_message_ui_found", + "unique": false, + "columnNames": [ + "ui_found" + ], + "createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)" + }, + { + "name": "index_message_ui_ignored", + "unique": false, + "columnNames": [ + "ui_ignored" + ], + "createSql": "CREATE INDEX `index_message_ui_ignored` ON `${TABLE_NAME}` (`ui_ignored`)" + }, + { + "name": "index_message_ui_browsed", + "unique": false, + "columnNames": [ + "ui_browsed" + ], + "createSql": "CREATE INDEX `index_message_ui_browsed` ON `${TABLE_NAME}` (`ui_browsed`)" + } + ], + "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, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folder", + "columnName": "folder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "args", + "columnName": "args", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_operation_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_operation_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)" + } + ], + "foreignKeys": [ + { + "table": "folder", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "folder" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "message" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "answer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "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, \"343e402af5a0888b893278879baad58d\")" + ] + } +} \ 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 bd0d0d3082..178af412d9 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 = 15, + version = 16, entities = { EntityIdentity.class, EntityAccount.class, @@ -241,6 +241,13 @@ public abstract class DB extends RoomDatabase { db.execSQL("ALTER TABLE `folder` ADD COLUMN `sync_state` TEXT"); } }) + .addMigrations(new Migration(15, 16) { + @Override + public void migrate(SupportSQLiteDatabase db) { + Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion); + db.execSQL("ALTER TABLE `folder` ADD COLUMN `poll` INTEGER NOT NULL DEFAULT 0"); + } + }) .build(); } diff --git a/app/src/main/java/eu/faircode/email/DaoFolder.java b/app/src/main/java/eu/faircode/email/DaoFolder.java index 61250fe447..3554681b4f 100644 --- a/app/src/main/java/eu/faircode/email/DaoFolder.java +++ b/app/src/main/java/eu/faircode/email/DaoFolder.java @@ -149,6 +149,7 @@ public interface DaoFolder { ", display = :display" + ", hide = :hide" + ", synchronize = :synchronize" + + ", poll = :poll" + ", unified = :unified" + ", `sync_days` = :sync_days" + ", `keep_days` = :keep_days" + @@ -158,6 +159,7 @@ public interface DaoFolder { String name, String display, boolean hide, boolean synchronize, + boolean poll, boolean unified, int sync_days, int keep_days); diff --git a/app/src/main/java/eu/faircode/email/EntityFolder.java b/app/src/main/java/eu/faircode/email/EntityFolder.java index bb197bef35..6e04dce384 100644 --- a/app/src/main/java/eu/faircode/email/EntityFolder.java +++ b/app/src/main/java/eu/faircode/email/EntityFolder.java @@ -69,6 +69,8 @@ public class EntityFolder implements Serializable { @NonNull public Boolean synchronize; @NonNull + public Boolean poll = false; + @NonNull public Integer sync_days; @NonNull public Integer keep_days; @@ -168,6 +170,9 @@ public class EntityFolder implements Serializable { this.type.equals(other.type) && this.level.equals(other.level) && this.synchronize.equals(other.synchronize) && + this.poll.equals(other.poll) && + this.sync_days.equals(other.sync_days) && + this.keep_days.equals(other.keep_days) && (this.display == null ? other.display == null : this.display.equals(other.display)) && this.hide == other.hide && this.unified == other.unified && @@ -189,6 +194,7 @@ public class EntityFolder implements Serializable { json.put("type", type); json.put("level", level); json.put("synchronize", synchronize); + json.put("poll", poll); json.put("sync_days", sync_days); json.put("keep_days", keep_days); json.put("display", display); @@ -208,6 +214,10 @@ public class EntityFolder implements Serializable { folder.level = 0; folder.synchronize = json.getBoolean("synchronize"); + if (json.has("poll")) + folder.poll = json.getBoolean("poll"); + else + folder.poll = false; if (json.has("after")) folder.sync_days = json.getInt("after"); diff --git a/app/src/main/java/eu/faircode/email/FragmentFolder.java b/app/src/main/java/eu/faircode/email/FragmentFolder.java index ed48d2c204..f1a5115c14 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolder.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolder.java @@ -29,6 +29,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ProgressBar; @@ -52,6 +53,7 @@ public class FragmentFolder extends FragmentEx { private EditText etDisplay; private CheckBox cbHide; private CheckBox cbSynchronize; + private CheckBox cbPoll; private CheckBox cbUnified; private EditText etSyncDays; private EditText etKeepDays; @@ -85,6 +87,7 @@ public class FragmentFolder extends FragmentEx { etDisplay = view.findViewById(R.id.etDisplay); cbHide = view.findViewById(R.id.cbHide); cbSynchronize = view.findViewById(R.id.cbSynchronize); + cbPoll = view.findViewById(R.id.cbPoll); cbUnified = view.findViewById(R.id.cbUnified); etSyncDays = view.findViewById(R.id.etSyncDays); etKeepDays = view.findViewById(R.id.etKeepDays); @@ -93,6 +96,13 @@ public class FragmentFolder extends FragmentEx { pbSave = view.findViewById(R.id.pbSave); pbWait = view.findViewById(R.id.pbWait); + cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + cbPoll.setEnabled(isChecked); + } + }); + btnSave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -109,6 +119,7 @@ public class FragmentFolder extends FragmentEx { args.putBoolean("hide", cbHide.isChecked()); args.putBoolean("unified", cbUnified.isChecked()); args.putBoolean("synchronize", cbSynchronize.isChecked()); + args.putBoolean("poll", cbPoll.isChecked()); args.putString("sync", etSyncDays.getText().toString()); args.putString("keep", etKeepDays.getText().toString()); @@ -122,6 +133,7 @@ public class FragmentFolder extends FragmentEx { boolean hide = args.getBoolean("hide"); boolean unified = args.getBoolean("unified"); boolean synchronize = args.getBoolean("synchronize"); + boolean poll = args.getBoolean("poll"); String sync = args.getString("sync"); String keep = args.getString("keep"); @@ -132,7 +144,8 @@ public class FragmentFolder extends FragmentEx { if (keep_days < sync_days) keep_days = sync_days; - EntityFolder folder = null; + boolean reload = false; + EntityFolder folder; IMAPStore istore = null; DB db = DB.getInstance(getContext()); @@ -167,6 +180,7 @@ public class FragmentFolder extends FragmentEx { create.type = EntityFolder.USER; create.unified = unified; create.synchronize = synchronize; + create.poll = poll; create.sync_days = sync_days; create.keep_days = keep_days; db.folder().insertFolder(create); @@ -182,6 +196,10 @@ public class FragmentFolder extends FragmentEx { } if (folder != null) { + reload = (!folder.name.equals(name) || + !folder.synchronize.equals(synchronize) || + !folder.poll.equals(poll)); + Calendar cal_keep = Calendar.getInstance(); cal_keep.add(Calendar.DAY_OF_MONTH, -keep_days); cal_keep.set(Calendar.HOUR_OF_DAY, 0); @@ -194,7 +212,12 @@ public class FragmentFolder extends FragmentEx { keep_time = 0; Log.i(Helper.TAG, "Updating folder=" + name); - db.folder().setFolderProperties(id, name, display, hide, synchronize, unified, sync_days, keep_days); + db.folder().setFolderProperties(id, + name, display, + hide, + synchronize, poll, + unified, + sync_days, keep_days); db.message().deleteMessagesBefore(id, keep_time, true); @@ -210,7 +233,7 @@ public class FragmentFolder extends FragmentEx { istore.close(); } - if (folder == null || !folder.name.equals(name)) + if (folder == null || !folder.name.equals(name) || reload) ServiceSynchronize.reload(getContext(), "save folder"); else EntityOperation.sync(db, folder.id); @@ -350,6 +373,7 @@ public class FragmentFolder extends FragmentEx { cbHide.setChecked(folder == null ? false : folder.hide); cbUnified.setChecked(folder == null ? false : folder.unified); cbSynchronize.setChecked(folder == null || folder.synchronize); + cbPoll.setChecked(folder == null ? false : folder.poll); etSyncDays.setText(Integer.toString(folder == null ? EntityFolder.DEFAULT_USER_SYNC : folder.sync_days)); etKeepDays.setText(Integer.toString(folder == null ? EntityFolder.DEFAULT_USER_SYNC : folder.keep_days)); } @@ -358,6 +382,7 @@ public class FragmentFolder extends FragmentEx { pbWait.setVisibility(View.GONE); Helper.setViewsEnabled(view, true); etRename.setEnabled(folder == null || EntityFolder.USER.equals(folder.type)); + cbPoll.setEnabled(cbSynchronize.isChecked()); btnSave.setEnabled(true); ibDelete.setVisibility(folder == null || !EntityFolder.USER.equals(folder.type) ? View.GONE : View.VISIBLE); } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 2263b49415..e92bac8045 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -886,53 +886,134 @@ public class ServiceSynchronize extends LifecycleService { // Update folder list synchronizeFolders(account, istore, state); - // Open folders - for (final EntityFolder folder : db.folder().getFolders(account.id, true)) { - Log.i(Helper.TAG, account.name + " sync folder " + folder.name); + // Open synchronizing folders + for (final EntityFolder folder : db.folder().getFolders(account.id)) { + if (folder.synchronize && !folder.poll && capIdle) { + Log.i(Helper.TAG, account.name + " sync folder " + folder.name); - db.folder().setFolderState(folder.id, "connecting"); + db.folder().setFolderState(folder.id, "connecting"); - final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name); - try { - ifolder.open(Folder.READ_WRITE); - } catch (Throwable ex) { - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - throw ex; - } - folders.put(folder, ifolder); + final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name); + try { + ifolder.open(Folder.READ_WRITE); + } catch (Throwable ex) { + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + throw ex; + } + folders.put(folder, ifolder); - db.folder().setFolderState(folder.id, "connected"); - db.folder().setFolderError(folder.id, null); + db.folder().setFolderState(folder.id, "connected"); + db.folder().setFolderError(folder.id, null); - Log.i(Helper.TAG, account.name + " folder " + folder.name + " flags=" + ifolder.getPermanentFlags()); + Log.i(Helper.TAG, account.name + " folder " + folder.name + " flags=" + ifolder.getPermanentFlags()); - // Listen for new and deleted messages - ifolder.addMessageCountListener(new MessageCountAdapter() { - @Override - public void messagesAdded(MessageCountEvent e) { - try { - wlAccount.acquire(); - Log.i(Helper.TAG, folder.name + " messages added"); - - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.ENVELOPE); - fp.add(FetchProfile.Item.FLAGS); - fp.add(FetchProfile.Item.CONTENT_INFO); // body structure - fp.add(UIDFolder.FetchProfileItem.UID); - fp.add(IMAPFolder.FetchProfileItem.HEADERS); - fp.add(IMAPFolder.FetchProfileItem.MESSAGE); - fp.add(FetchProfile.Item.SIZE); - fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE); - ifolder.fetch(e.getMessages(), fp); - - for (Message imessage : e.getMessages()) + // Listen for new and deleted messages + ifolder.addMessageCountListener(new MessageCountAdapter() { + @Override + public void messagesAdded(MessageCountEvent e) { + try { + wlAccount.acquire(); + Log.i(Helper.TAG, folder.name + " messages added"); + + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.ENVELOPE); + fp.add(FetchProfile.Item.FLAGS); + fp.add(FetchProfile.Item.CONTENT_INFO); // body structure + fp.add(UIDFolder.FetchProfileItem.UID); + fp.add(IMAPFolder.FetchProfileItem.HEADERS); + fp.add(IMAPFolder.FetchProfileItem.MESSAGE); + fp.add(FetchProfile.Item.SIZE); + fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE); + ifolder.fetch(e.getMessages(), fp); + + for (Message imessage : e.getMessages()) + try { + long id; + try { + db.beginTransaction(); + id = synchronizeMessage( + ServiceSynchronize.this, + folder, ifolder, (IMAPMessage) imessage, + false, false, false); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + try { + db.beginTransaction(); + downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, id); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } catch (MessageRemovedException ex) { + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } catch (IOException ex) { + if (ex.getCause() instanceof MessageRemovedException) + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + else + throw ex; + } + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account, folder, ex); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + state.error(); + } finally { + wlAccount.release(); + } + } + + @Override + public void messagesRemoved(MessageCountEvent e) { + try { + wlAccount.acquire(); + Log.i(Helper.TAG, folder.name + " messages removed"); + for (Message imessage : e.getMessages()) + try { + long uid = ifolder.getUID(imessage); + + DB db = DB.getInstance(ServiceSynchronize.this); + int count = db.message().deleteMessage(folder.id, uid); + + Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count); + } catch (MessageRemovedException ex) { + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account, folder, ex); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + state.error(); + } finally { + wlAccount.release(); + } + } + }); + + // Flags (like "seen") at the remote could be changed while synchronizing + + // Listen for changed messages + ifolder.addMessageChangedListener(new MessageChangedListener() { + @Override + public void messageChanged(MessageChangedEvent e) { + try { + wlAccount.acquire(); try { + Log.i(Helper.TAG, folder.name + " message changed"); + + FetchProfile fp = new FetchProfile(); + fp.add(UIDFolder.FetchProfileItem.UID); + fp.add(IMAPFolder.FetchProfileItem.FLAGS); + ifolder.fetch(new Message[]{e.getMessage()}, fp); + long id; try { db.beginTransaction(); id = synchronizeMessage( ServiceSynchronize.this, - folder, ifolder, (IMAPMessage) imessage, + folder, ifolder, (IMAPMessage) e.getMessage(), false, false, false); db.setTransactionSuccessful(); } finally { @@ -941,7 +1022,7 @@ public class ServiceSynchronize extends LifecycleService { try { db.beginTransaction(); - downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, id); + downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), id); db.setTransactionSuccessful(); } finally { db.endTransaction(); @@ -954,99 +1035,18 @@ public class ServiceSynchronize extends LifecycleService { else throw ex; } - } catch (Throwable ex) { - Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account, folder, ex); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - state.error(); - } finally { - wlAccount.release(); - } - } - - @Override - public void messagesRemoved(MessageCountEvent e) { - try { - wlAccount.acquire(); - Log.i(Helper.TAG, folder.name + " messages removed"); - for (Message imessage : e.getMessages()) - try { - long uid = ifolder.getUID(imessage); - - DB db = DB.getInstance(ServiceSynchronize.this); - int count = db.message().deleteMessage(folder.id, uid); - - Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count); - } catch (MessageRemovedException ex) { - Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - } - } catch (Throwable ex) { - Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account, folder, ex); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - state.error(); - } finally { - wlAccount.release(); - } - } - }); - - // Flags (like "seen") at the remote could be changed while synchronizing - - // Listen for changed messages - ifolder.addMessageChangedListener(new MessageChangedListener() { - @Override - public void messageChanged(MessageChangedEvent e) { - try { - wlAccount.acquire(); - try { - Log.i(Helper.TAG, folder.name + " message changed"); - - FetchProfile fp = new FetchProfile(); - fp.add(UIDFolder.FetchProfileItem.UID); - fp.add(IMAPFolder.FetchProfileItem.FLAGS); - ifolder.fetch(new Message[]{e.getMessage()}, fp); - - long id; - try { - db.beginTransaction(); - id = synchronizeMessage( - ServiceSynchronize.this, - folder, ifolder, (IMAPMessage) e.getMessage(), - false, false, false); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - try { - db.beginTransaction(); - downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), id); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } catch (MessageRemovedException ex) { - Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - } catch (IOException ex) { - if (ex.getCause() instanceof MessageRemovedException) - Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - else - throw ex; + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account, folder, ex); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + state.error(); + } finally { + wlAccount.release(); } - } catch (Throwable ex) { - Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account, folder, ex); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - state.error(); - } finally { - wlAccount.release(); } - } - }); + }); - // Idle folder - if (capIdle) { + // Idle folder Thread idler = new Thread(new Runnable() { @Override public void run() { @@ -1068,13 +1068,12 @@ public class ServiceSynchronize extends LifecycleService { }, "idler." + folder.id); idler.start(); idlers.add(idler); - } - EntityOperation.sync(db, folder.id); - } + EntityOperation.sync(db, folder.id); + } else + folders.put(folder, null); - // Observe folder operations - for (final EntityFolder folder : db.folder().getFolders(account.id)) { + // Observe operations Handler handler = new Handler(getMainLooper()) { private List handling = new ArrayList<>(); private final PowerManager.WakeLock wlFolder = pm.newWakeLock( @@ -1109,55 +1108,50 @@ public class ServiceSynchronize extends LifecycleService { Log.i(Helper.TAG, folder.name + " process"); // Get folder - EntityFolder ofolder = null; IMAPFolder ifolder = null; for (EntityFolder f : folders.keySet()) if (f.id.equals(folder.id)) { - ofolder = f; - ifolder = folders.get(f); + ifolder = folders.get(f); // null when polling break; } - final boolean shouldClose = (ofolder == null); + final boolean shouldClose = (ifolder == null); try { - if (ofolder == null) - ofolder = db.folder().getFolder(folder.id); - - Log.i(Helper.TAG, ofolder.name + " run " + (shouldClose ? "offline" : "online")); + Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online")); if (ifolder == null) { // Prevent unnecessary folder connections - if (db.operation().getOperationCount(ofolder.id, null) == 0) + if (db.operation().getOperationCount(folder.id, null) == 0) return; - db.folder().setFolderState(ofolder.id, "connecting"); + db.folder().setFolderState(folder.id, "connecting"); - ifolder = (IMAPFolder) istore.getFolder(ofolder.name); + ifolder = (IMAPFolder) istore.getFolder(folder.name); ifolder.open(Folder.READ_WRITE); - db.folder().setFolderState(ofolder.id, "connected"); - db.folder().setFolderError(ofolder.id, null); + db.folder().setFolderState(folder.id, "connected"); + db.folder().setFolderError(folder.id, null); } - processOperations(account, ofolder, isession, istore, ifolder, state); + processOperations(account, folder, isession, istore, ifolder, state); } catch (Throwable ex) { - Log.e(Helper.TAG, ofolder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account, ofolder, ex); - db.folder().setFolderError(ofolder.id, Helper.formatThrowable(ex)); + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account, folder, ex); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); state.error(); } finally { if (shouldClose) { if (ifolder != null && ifolder.isOpen()) { - db.folder().setFolderState(ofolder.id, "closing"); + db.folder().setFolderState(folder.id, "closing"); try { ifolder.close(false); } catch (MessagingException ex) { - Log.w(Helper.TAG, ofolder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); } } - db.folder().setFolderState(ofolder.id, null); + db.folder().setFolderState(folder.id, null); } } } finally { @@ -1170,6 +1164,8 @@ public class ServiceSynchronize extends LifecycleService { }); } }; + + // Start watching for operations handler.sendEmptyMessage(1); handlers.add(handler); } @@ -1197,11 +1193,12 @@ public class ServiceSynchronize extends LifecycleService { throw new StoreClosedException(istore); for (EntityFolder folder : folders.keySet()) - if (capIdle) { - if (!folders.get(folder).isOpen()) - throw new FolderClosedException(folders.get(folder)); - } else - synchronizeMessages(account, folder, folders.get(folder), state); + if (folder.synchronize) + if (!folder.poll && capIdle) { + if (!folders.get(folder).isOpen()) + throw new FolderClosedException(folders.get(folder)); + } else + EntityOperation.sync(db, folder.id); // Successfully connected: reset back off time backoff = CONNECT_BACKOFF_START; @@ -1230,10 +1227,6 @@ public class ServiceSynchronize extends LifecycleService { // Cleanup am.cancel(pi); unregisterReceiver(alarm); - - for (Handler handler : handlers) - handler.sendEmptyMessage(0); - handlers.clear(); } Log.i(Helper.TAG, account.name + " done state=" + state); @@ -1244,10 +1237,16 @@ public class ServiceSynchronize extends LifecycleService { EntityLog.log(ServiceSynchronize.this, account.name + " " + Helper.formatThrowable(ex)); db.account().setAccountError(account.id, Helper.formatThrowable(ex)); } finally { + // Stop watching for operations + for (Handler handler : handlers) + handler.sendEmptyMessage(0); + handlers.clear(); + EntityLog.log(this, account.name + " closing"); db.account().setAccountState(account.id, "closing"); for (EntityFolder folder : folders.keySet()) - db.folder().setFolderState(folder.id, "closing"); + if (folder.synchronize && !folder.poll) + db.folder().setFolderState(folder.id, "closing"); // Close store try { @@ -1267,7 +1266,8 @@ public class ServiceSynchronize extends LifecycleService { idlers.clear(); for (EntityFolder folder : folders.keySet()) - db.folder().setFolderState(folder.id, null); + if (folder.synchronize && !folder.poll) + db.folder().setFolderState(folder.id, null); } if (state.running()) diff --git a/app/src/main/res/layout/fragment_folder.xml b/app/src/main/res/layout/fragment_folder.xml index 08624e904f..8eba92bada 100644 --- a/app/src/main/res/layout/fragment_folder.xml +++ b/app/src/main/res/layout/fragment_folder.xml @@ -80,6 +80,15 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cbUnified" /> + + + app:layout_constraintTop_toBottomOf="@id/cbPoll" /> Hide hidden folders Show hidden folders Synchronize (receive messages) + Poll instead of synchronize Show in unified inbox Synchronize messages (days) Keep messages (days)