Revised operation handling

pull/30/head
M66B 6 years ago
parent 659900f6d7
commit 70a2fdee1c

@ -1,8 +1,8 @@
{
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "74b765c4ee277861c68851c33a596610",
"version": 1,
"identityHash": "ff81778e939ac6480ab8ec93435db96a",
"entities": [
{
"tableName": "identity",
@ -634,7 +634,7 @@
},
{
"tableName": "operation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"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, 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",
@ -642,6 +642,12 @@
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "folder",
"columnName": "folder",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
@ -678,6 +684,17 @@
}
],
"foreignKeys": [
{
"table": "folder",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"folder"
],
"referencedColumns": [
"id"
]
},
{
"table": "message",
"onDelete": "CASCADE",
@ -694,7 +711,7 @@
],
"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, \"74b765c4ee277861c68851c33a596610\")"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"ff81778e939ac6480ab8ec93435db96a\")"
]
}
}

@ -1,639 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "28de77abd152c62bbdcca05efc2a6f59",
"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, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"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": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "port",
"columnName": "port",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starttls",
"columnName": "starttls",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "user",
"columnName": "user",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"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": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"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, `after` 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": 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": "after",
"columnName": "after",
"affinity": "INTEGER",
"notNull": true
}
],
"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`)"
}
],
"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, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `body` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, 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 CASCADE , FOREIGN KEY(`replying`) 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": "identity",
"columnName": "identity",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replying",
"columnName": "replying",
"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": "inreplyto",
"columnName": "inreplyto",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thread",
"columnName": "thread",
"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": "subject",
"columnName": "subject",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sent",
"columnName": "sent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "received",
"columnName": "received",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "seen",
"columnName": "seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_seen",
"columnName": "ui_seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_hide",
"columnName": "ui_hide",
"affinity": "INTEGER",
"notNull": true
}
],
"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_folder_uid",
"unique": true,
"columnNames": [
"folder",
"uid"
],
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
},
{
"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`)"
}
],
"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": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"identity"
],
"referencedColumns": [
"id"
]
},
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"replying"
],
"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, `type` TEXT NOT NULL, `name` TEXT, `content` BLOB, 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": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_attachment_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
},
{
"name": "index_attachment_message_sequence",
"unique": true,
"columnNames": [
"message",
"sequence"
],
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
}
],
"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, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` 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": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "args",
"columnName": "args",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_operation_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
}
],
"foreignKeys": [
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"message"
],
"referencedColumns": [
"id"
]
}
]
}
],
"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, \"28de77abd152c62bbdcca05efc2a6f59\")"
]
}
}

@ -1,651 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "40fd6ebf37a522fd68104290c7f30208",
"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, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"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": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "port",
"columnName": "port",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starttls",
"columnName": "starttls",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "user",
"columnName": "user",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"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": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"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, `after` 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": 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": "after",
"columnName": "after",
"affinity": "INTEGER",
"notNull": true
}
],
"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`)"
}
],
"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, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `body` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, 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 CASCADE , FOREIGN KEY(`replying`) 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": "identity",
"columnName": "identity",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replying",
"columnName": "replying",
"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": "inreplyto",
"columnName": "inreplyto",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thread",
"columnName": "thread",
"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": "subject",
"columnName": "subject",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sent",
"columnName": "sent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "received",
"columnName": "received",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "seen",
"columnName": "seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_seen",
"columnName": "ui_seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_hide",
"columnName": "ui_hide",
"affinity": "INTEGER",
"notNull": true
}
],
"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_folder_uid",
"unique": true,
"columnNames": [
"folder",
"uid"
],
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
},
{
"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`)"
}
],
"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": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"identity"
],
"referencedColumns": [
"id"
]
},
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"replying"
],
"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, `size` INTEGER, `progress` INTEGER, `content` BLOB, 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": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "progress",
"columnName": "progress",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_attachment_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
},
{
"name": "index_attachment_message_sequence",
"unique": true,
"columnNames": [
"message",
"sequence"
],
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
}
],
"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, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` 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": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "args",
"columnName": "args",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_operation_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
}
],
"foreignKeys": [
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"message"
],
"referencedColumns": [
"id"
]
}
]
}
],
"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, \"40fd6ebf37a522fd68104290c7f30208\")"
]
}
}

@ -1,659 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "ed5cc04cd8d171bff391cd8435cc29fe",
"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, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"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": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "port",
"columnName": "port",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starttls",
"columnName": "starttls",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "user",
"columnName": "user",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"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": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"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, `after` 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": 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": "after",
"columnName": "after",
"affinity": "INTEGER",
"notNull": true
}
],
"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`)"
}
],
"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, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `body` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, 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 CASCADE , FOREIGN KEY(`replying`) 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": "identity",
"columnName": "identity",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replying",
"columnName": "replying",
"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": "inreplyto",
"columnName": "inreplyto",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thread",
"columnName": "thread",
"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": "subject",
"columnName": "subject",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sent",
"columnName": "sent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "received",
"columnName": "received",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "seen",
"columnName": "seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_seen",
"columnName": "ui_seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_hide",
"columnName": "ui_hide",
"affinity": "INTEGER",
"notNull": true
}
],
"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_folder_uid",
"unique": true,
"columnNames": [
"folder",
"uid"
],
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
},
{
"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`)"
}
],
"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": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"identity"
],
"referencedColumns": [
"id"
]
},
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"replying"
],
"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, `size` INTEGER, `progress` INTEGER, `content` BLOB, 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": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "progress",
"columnName": "progress",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_attachment_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
},
{
"name": "index_attachment_message_sequence",
"unique": true,
"columnNames": [
"message",
"sequence"
],
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
}
],
"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, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` 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": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "args",
"columnName": "args",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_operation_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
}
],
"foreignKeys": [
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"message"
],
"referencedColumns": [
"id"
]
}
]
}
],
"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, \"ed5cc04cd8d171bff391cd8435cc29fe\")"
]
}
}

@ -1,667 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "b14a2bd7371400904e10cb3dd32a5d98",
"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, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"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": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "port",
"columnName": "port",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starttls",
"columnName": "starttls",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "user",
"columnName": "user",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"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": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"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, `after` 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": 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": "after",
"columnName": "after",
"affinity": "INTEGER",
"notNull": true
}
],
"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`)"
}
],
"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, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `body` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, 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 CASCADE , FOREIGN KEY(`replying`) 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": "identity",
"columnName": "identity",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replying",
"columnName": "replying",
"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": "inreplyto",
"columnName": "inreplyto",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thread",
"columnName": "thread",
"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": "subject",
"columnName": "subject",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sent",
"columnName": "sent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "received",
"columnName": "received",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "seen",
"columnName": "seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_seen",
"columnName": "ui_seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_hide",
"columnName": "ui_hide",
"affinity": "INTEGER",
"notNull": true
}
],
"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_folder_uid",
"unique": true,
"columnNames": [
"folder",
"uid"
],
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
},
{
"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`)"
}
],
"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": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"identity"
],
"referencedColumns": [
"id"
]
},
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"replying"
],
"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, `size` INTEGER, `progress` INTEGER, `content` BLOB, 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": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "progress",
"columnName": "progress",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_attachment_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
},
{
"name": "index_attachment_message_sequence",
"unique": true,
"columnNames": [
"message",
"sequence"
],
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
}
],
"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, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` 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": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "args",
"columnName": "args",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_operation_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
}
],
"foreignKeys": [
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"message"
],
"referencedColumns": [
"id"
]
}
]
}
],
"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, \"b14a2bd7371400904e10cb3dd32a5d98\")"
]
}
}

@ -1,673 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "5b850d93ed77b9df16fbe138b3fb2475",
"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, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL)",
"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": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "port",
"columnName": "port",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starttls",
"columnName": "starttls",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "user",
"columnName": "user",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `seen_until` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"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": "primary",
"columnName": "primary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "synchronize",
"columnName": "synchronize",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "seen_until",
"columnName": "seen_until",
"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, `synchronize` INTEGER NOT NULL, `after` 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": 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": "after",
"columnName": "after",
"affinity": "INTEGER",
"notNull": true
}
],
"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`)"
}
],
"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, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `body` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, 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 CASCADE , FOREIGN KEY(`replying`) 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": "identity",
"columnName": "identity",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "replying",
"columnName": "replying",
"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": "inreplyto",
"columnName": "inreplyto",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thread",
"columnName": "thread",
"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": "subject",
"columnName": "subject",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sent",
"columnName": "sent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "received",
"columnName": "received",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "seen",
"columnName": "seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_seen",
"columnName": "ui_seen",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ui_hide",
"columnName": "ui_hide",
"affinity": "INTEGER",
"notNull": true
}
],
"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_folder_uid",
"unique": true,
"columnNames": [
"folder",
"uid"
],
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
},
{
"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`)"
}
],
"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": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"identity"
],
"referencedColumns": [
"id"
]
},
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"replying"
],
"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, `size` INTEGER, `progress` INTEGER, `content` BLOB, 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": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "progress",
"columnName": "progress",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_attachment_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
},
{
"name": "index_attachment_message_sequence",
"unique": true,
"columnNames": [
"message",
"sequence"
],
"createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
}
],
"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, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` 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": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "args",
"columnName": "args",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_operation_message",
"unique": false,
"columnNames": [
"message"
],
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
}
],
"foreignKeys": [
{
"table": "message",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"message"
],
"referencedColumns": [
"id"
]
}
]
}
],
"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, \"5b850d93ed77b9df16fbe138b3fb2475\")"
]
}
}

@ -38,6 +38,7 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.text.Collator;
import java.util.Collections;
@ -45,8 +46,6 @@ import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -62,13 +61,17 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
private DrawerLayout drawerLayout;
private ListView drawerList;
private ActionBarDrawerToggle drawerToggle;
private ExecutorService executor = Executors.newSingleThreadExecutor();
static final int LOADER_ACCOUNT_PUT = 1;
static final int LOADER_IDENTITY_PUT = 2;
static final int LOADER_FOLDER_PUT = 3;
static final int LOADER_MESSAGES_INIT = 4;
static final int LOADER_MESSAGE_MOVE = 5;
static final int LOADER_MESSAGE_SEEN = 4;
static final int LOADER_MESSAGE_EDIT = 5;
static final int LOADER_MESSAGE_SPAM = 6;
static final int LOADER_MESSAGE_TRASH = 7;
static final int LOADER_MESSAGE_MOVE = 8;
static final int LOADER_MESSAGE_ARCHIVE = 9;
static final int LOADER_SEEN_UNTIL = 10;
static final int REQUEST_VIEW = 1;
static final int REQUEST_UNSEEN = 2;
@ -271,19 +274,27 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
setIntent(intent);
if ("unseen".equals(action)) {
final long now = new Date().getTime();
Bundle args = new Bundle();
args.putLong("time", new Date().getTime());
executor.submit(new Runnable() {
new SimpleLoader() {
@Override
public void run() {
public Object onLoad(Bundle args) {
long time = args.getLong("time");
DaoAccount dao = DB.getInstance(ActivityView.this).account();
for (EntityAccount account : dao.getAccounts(true)) {
account.seen_until = now;
account.seen_until = time;
dao.updateAccount(account);
}
Log.i(Helper.TAG, "Updated seen until");
return null;
}
});
@Override
public void onLoaded(Bundle args, Result result) {
if (result.ex != null)
Toast.makeText(ActivityView.this, result.ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(this, LOADER_SEEN_UNTIL, args);
}
}
@ -420,11 +431,50 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("messages");
fragmentTransaction.commit();
} else if (ACTION_VIEW_MESSAGE.equals(intent.getAction())) {
FragmentMessage fragment = new FragmentMessage();
fragment.setArguments(intent.getExtras());
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("message");
fragmentTransaction.commit();
new SimpleLoader() {
@Override
public Object onLoad(Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(ActivityView.this);
EntityMessage message = db.message().getMessage(id);
EntityFolder folder = db.folder().getFolder(message.folder);
if (!EntityFolder.OUTBOX.equals(folder.type)) {
if (!message.seen && !message.ui_seen) {
try {
db.beginTransaction();
message.ui_seen = !message.ui_seen;
db.message().updateMessage(message);
if (message.uid != null)
EntityOperation.queue(db, message, EntityOperation.SEEN, message.ui_seen);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(ActivityView.this);
}
}
return null;
}
@Override
public void onLoaded(Bundle args, Result result) {
if (result.ex == null) {
FragmentMessage fragment = new FragmentMessage();
fragment.setArguments(args);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("message");
fragmentTransaction.commit();
} else
Toast.makeText(ActivityView.this, result.ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(ActivityView.this, 0000, intent.getExtras());
} else if (ACTION_EDIT_FOLDER.equals(intent.getAction())) {
FragmentFolder fragment = new FragmentFolder();
fragment.setArguments(intent.getExtras());

@ -177,10 +177,12 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
executor.submit(new Runnable() {
@Override
public void run() {
// No need for a transaction
DB db = DB.getInstance(context);
db.attachment().setProgress(attachment.id, 0);
EntityMessage message = db.message().getMessage(attachment.message);
EntityOperation.queue(context, message, EntityOperation.ATTACHMENT, attachment.sequence);
EntityOperation.queue(db, message, EntityOperation.ATTACHMENT, attachment.sequence);
EntityOperation.process(context);
}
});

@ -24,7 +24,6 @@ import android.content.Intent;
import android.graphics.Typeface;
import android.preference.PreferenceManager;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -32,9 +31,6 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.paging.PagedListAdapter;
@ -45,7 +41,6 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
private Context context;
private ViewType viewType;
private boolean debug;
private ExecutorService executor = Executors.newCachedThreadPool();
enum ViewType {FOLDER, THREAD}
@ -133,34 +128,16 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
return;
final TupleMessageEx message = getItem(pos);
executor.submit(new Runnable() {
@Override
public void run() {
try {
if (EntityFolder.DRAFTS.equals(message.folderType))
context.startActivity(
new Intent(context, ActivityCompose.class)
.putExtra("id", message.id));
else {
if (!EntityFolder.OUTBOX.equals(message.folderType)) {
if (!message.seen && !message.ui_seen) {
message.ui_seen = !message.ui_seen;
DB.getInstance(context).message().updateMessage(message);
EntityOperation.queue(context, message, EntityOperation.SEEN, message.ui_seen);
EntityOperation.process(context);
}
}
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_VIEW_MESSAGE)
.putExtra("id", message.id));
}
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
}
});
if (EntityFolder.DRAFTS.equals(message.folderType))
context.startActivity(
new Intent(context, ActivityCompose.class)
.putExtra("id", message.id));
else {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_VIEW_MESSAGE)
.putExtra("id", message.id));
}
}
}

@ -44,7 +44,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 7,
version = 1,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -71,7 +71,7 @@ public abstract class DB extends RoomDatabase {
private static DB sInstance;
private static final String DB_NAME = "email.db";
private static final String DB_NAME = "email.2.db";
public static synchronized DB getInstance(Context context) {
if (sInstance == null)
@ -92,12 +92,12 @@ public abstract class DB extends RoomDatabase {
super.onOpen(db);
}
})
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_3_4)
.addMigrations(MIGRATION_4_5)
.addMigrations(MIGRATION_5_6)
.addMigrations(MIGRATION_6_7)
//.addMigrations(MIGRATION_1_2)
//.addMigrations(MIGRATION_2_3)
//.addMigrations(MIGRATION_3_4)
//.addMigrations(MIGRATION_4_5)
//.addMigrations(MIGRATION_5_6)
//.addMigrations(MIGRATION_6_7)
.build();
}
@ -153,6 +153,7 @@ public abstract class DB extends RoomDatabase {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
// Recreate is sometimes causing problems with ROOM
db.execSQL("DROP TABLE `identity`");
db.execSQL("CREATE TABLE `identity`" +
" (`id` INTEGER PRIMARY KEY AUTOINCREMENT" +

@ -80,6 +80,11 @@ public interface DaoMessage {
@Query("SELECT * FROM message WHERE folder = :folder AND uid = :uid")
EntityMessage getMessage(long folder, long uid);
@Query("SELECT * FROM message" +
" JOIN folder on folder.id = message.folder" +
" WHERE thread = :thread AND folder.type= '" + EntityFolder.ARCHIVE + "'")
EntityMessage getArchivedMessage(String thread);
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_hide) AS count" +
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_hide AND NOT m.ui_seen) AS unseen" +
@ -99,7 +104,7 @@ public interface DaoMessage {
void updateMessage(EntityMessage message);
@Query("DELETE FROM message WHERE id = :id")
void deleteMessage(long id);
int deleteMessage(long id);
@Query("DELETE FROM message WHERE folder = :folder AND uid = :uid")
int deleteMessage(long folder, long uid);

@ -31,15 +31,10 @@ public interface DaoOperation {
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertOperation(EntityOperation operation);
@Query("SELECT operation.*, message.uid FROM operation" +
" JOIN message ON message.id = operation.message" +
" WHERE folder = :folder" +
" ORDER BY operation.id")
List<TupleOperationEx> getOperations(long folder);
@Query("SELECT COUNT(operation.id) FROM operation" +
" JOIN message ON message.id = operation.message" +
" WHERE folder = :folder")
@Query("SELECT * FROM operation WHERE folder = :folder ORDER BY id")
List<EntityOperation> getOperations(long folder);
@Query("SELECT COUNT(id) FROM operation WHERE folder = :folder")
int getOperationCount(long folder);
@Query("DELETE FROM operation WHERE id = :id")

@ -40,6 +40,7 @@ import static androidx.room.ForeignKey.CASCADE;
@Entity(
tableName = EntityOperation.TABLE_NAME,
foreignKeys = {
@ForeignKey(childColumns = "folder", entity = EntityFolder.class, parentColumns = "id", onDelete = CASCADE),
@ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
},
indices = {
@ -52,6 +53,8 @@ public class EntityOperation {
@PrimaryKey(autoGenerate = true)
public Long id;
@NonNull
public Long folder;
@NonNull
public Long message;
@NonNull
public String name;
@ -66,25 +69,31 @@ public class EntityOperation {
private static List<Intent> queue = new ArrayList<>();
static void queue(Context context, EntityMessage message, String name) {
static void queue(DB db, EntityMessage message, String name) {
JSONArray jsonArray = new JSONArray();
queue(context, message, name, jsonArray);
queue(db, message, name, jsonArray);
}
static void queue(Context context, EntityMessage message, String name, Object value) {
static void queue(DB db, EntityMessage message, String name, Object value) {
JSONArray jsonArray = new JSONArray();
jsonArray.put(value);
queue(context, message, name, jsonArray);
queue(db, message, name, jsonArray);
}
private static void queue(Context context, EntityMessage message, String name, JSONArray jsonArray) {
DaoOperation dao = DB.getInstance(context).operation();
static void queue(DB db, EntityMessage message, String name, Object value1, Object value2) {
JSONArray jsonArray = new JSONArray();
jsonArray.put(value1);
jsonArray.put(value2);
queue(db, message, name, jsonArray);
}
private static void queue(DB db, EntityMessage message, String name, JSONArray jsonArray) {
EntityOperation operation = new EntityOperation();
operation.folder = message.folder;
operation.message = message.id;
operation.name = name;
operation.args = jsonArray.toString();
operation.id = dao.insertOperation(operation);
operation.id = db.operation().insertOperation(operation);
Intent intent = new Intent();
if (SEND.equals(name))
@ -99,9 +108,9 @@ public class EntityOperation {
queue.add(intent);
}
Log.i(Helper.TAG, "Queued op=" + operation.id + "/" + name +
" args=" + operation.args +
" msg=" + message.folder + "/" + message.id + " uid=" + message.uid);
Log.i(Helper.TAG, "Queued op=" + operation.id + "/" + operation.name +
" msg=" + message.folder + "/" + operation.message +
" args=" + operation.args);
}
public static void process(Context context) {

@ -80,9 +80,6 @@ public class FragmentAbout extends FragmentEx {
draft.ui_hide = false;
draft.id = db.message().insertMessage(draft);
EntityOperation.queue(getContext(), draft, EntityOperation.ADD);
EntityOperation.process(getContext());
startActivity(new Intent(getContext(), ActivityCompose.class)
.putExtra("id", draft.id));
}

@ -329,10 +329,10 @@ public class FragmentCompose extends FragmentEx {
try {
String action = args.getString("action");
long id = args.getLong("id", -1);
EntityMessage msg = DB.getInstance(getContext()).message().getMessage(id);
result.putString("action", action);
EntityMessage msg = DB.getInstance(getContext()).message().getMessage(id);
if (msg != null) {
if (msg.identity != null)
result.putLong("iid", msg.identity);
@ -550,47 +550,72 @@ public class FragmentCompose extends FragmentEx {
draft.received = new Date().getTime();
draft.seen = false;
draft.ui_seen = false;
draft.ui_hide = !"save".equals(action);
draft.ui_hide = false;
// Store draft
if (update)
message.updateMessage(draft);
else
if (!update)
draft.id = message.insertMessage(draft);
// Check data
try {
db.beginTransaction();
if ("send".equals(action)) {
if (draft.to == null && draft.cc == null && draft.bcc == null)
throw new IllegalArgumentException(getContext().getString(R.string.title_to_missing));
EntityOperation.queue(getContext(), draft, EntityOperation.DELETE);
if ("save".equals(action)) {
// Delete previous draft
if (draft.uid == null)
db.message().deleteMessage(draft.id);
else {
draft.ui_hide = true;
db.message().updateMessage(draft);
EntityOperation.queue(db, draft, EntityOperation.DELETE);
}
// Create new draft
draft.id = null;
draft.folder = folder.getOutbox().id;
draft.uid = null;
draft.ui_hide = false;
draft.id = db.message().insertMessage(draft);
EntityOperation.queue(db, draft, EntityOperation.ADD);
EntityOperation.queue(getContext(), draft, EntityOperation.SEND);
} else if ("trash".equals(action)) {
EntityFolder trash = db.folder().getFolderByType(ident.account, EntityFolder.TRASH);
} else if ("save".equals(action))
EntityOperation.queue(getContext(), draft, EntityOperation.ADD);
boolean move = (draft.uid != null);
if (move)
EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id, draft.uid);
else if ("trash".equals(action)) {
EntityOperation.queue(getContext(), draft, EntityOperation.DELETE);
draft.folder = trash.id;
draft.uid = null;
db.message().updateMessage(draft);
EntityFolder trash = db.folder().getFolderByType(ident.account, EntityFolder.TRASH);
if (trash != null) {
draft.id = null;
draft.folder = trash.id;
draft.id = db.message().insertMessage(draft);
if (!move)
EntityOperation.queue(db, draft, EntityOperation.ADD);
} else if ("send".equals(action)) {
if (draft.to == null && draft.cc == null && draft.bcc == null)
throw new IllegalArgumentException(getContext().getString(R.string.title_to_missing));
EntityOperation.queue(getContext(), draft, EntityOperation.ADD);
// Delete draft (cannot move to outbox)
if (draft.uid == null)
db.message().deleteMessage(draft.id);
else {
draft.ui_hide = true;
db.message().updateMessage(draft);
EntityOperation.queue(db, draft, EntityOperation.DELETE);
}
// Copy message to outbox
draft.id = null;
draft.folder = folder.getOutbox().id;
draft.uid = null;
draft.ui_hide = false;
draft.id = db.message().insertMessage(draft);
EntityOperation.queue(db, draft, EntityOperation.SEND);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@ -625,17 +650,20 @@ public class FragmentCompose extends FragmentEx {
String action = args.getString("action");
Log.i(Helper.TAG, "Put finished action=" + action + " ex=" + ex);
bottom_navigation.getMenu().setGroupEnabled(0, true);
if (ex == null) {
getFragmentManager().popBackStack();
if ("trash".equals(action))
if ("trash".equals(action)) {
getFragmentManager().popBackStack();
Toast.makeText(getContext(), R.string.title_draft_trashed, Toast.LENGTH_LONG).show();
else if ("save".equals(action))
} else if ("save".equals(action))
Toast.makeText(getContext(), R.string.title_draft_saved, Toast.LENGTH_LONG).show();
else if ("send".equals(action))
else if ("send".equals(action)) {
getFragmentManager().popBackStack();
Toast.makeText(getContext(), R.string.title_queued, Toast.LENGTH_LONG).show();
}
} else {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
bottom_navigation.getMenu().setGroupEnabled(0, true);
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
}
}

@ -23,6 +23,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -32,7 +33,6 @@ import android.text.Spannable;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -42,6 +42,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.bottomnavigation.BottomNavigationView;
@ -53,8 +54,6 @@ import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -90,7 +89,6 @@ public class FragmentMessage extends FragmentEx {
private AdapterAttachment adapter;
private ExecutorService executor = Executors.newCachedThreadPool();
private DateFormat df = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
@Override
@ -257,13 +255,15 @@ public class FragmentMessage extends FragmentEx {
} else {
setSubtitle(Helper.localizeFolderName(getContext(), message.folderName));
String extra = (debug ? (message.ui_hide ? "HIDDEN " : "") + message.uid + "/" + message.id + " " : "");
tvFrom.setText(message.from == null ? null : TextUtils.join(", ", message.from));
tvTo.setText(message.to == null ? null : TextUtils.join(", ", message.to));
tvCc.setText(message.cc == null ? null : TextUtils.join(", ", message.cc));
tvBcc.setText(message.bcc == null ? null : TextUtils.join(", ", message.bcc));
tvTime.setText(message.sent == null ? null : df.format(new Date(message.sent)));
tvSubject.setText(message.subject);
tvCount.setText(Integer.toString(message.count));
tvCount.setText(extra + Integer.toString(message.count));
tvCount.setVisibility(debug || message.count > 1 ? View.VISIBLE : View.GONE);
int typeface = (message.ui_seen ? Typeface.NORMAL : Typeface.BOLD);
@ -297,19 +297,17 @@ public class FragmentMessage extends FragmentEx {
? null
: Html.fromHtml(HtmlHelper.sanitize(getContext(), message.body, false)));
bottom_navigation.setTag(message.folderType);
db.folder().liveFolders(message.account).removeObservers(getViewLifecycleOwner());
db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {
@Override
public void onChanged(@Nullable final List<TupleFolderEx> folders) {
boolean inbox = EntityFolder.INBOX.equals(message.folderType);
boolean outbox = EntityFolder.OUTBOX.equals(message.folderType);
boolean archive = EntityFolder.ARCHIVE.equals(message.folderType);
boolean drafts = EntityFolder.DRAFTS.equals(message.folderType);
boolean trash = EntityFolder.TRASH.equals(message.folderType);
boolean junk = EntityFolder.JUNK.equals(message.folderType);
boolean sent = EntityFolder.SENT.equals(message.folderType);
boolean inInbox = EntityFolder.INBOX.equals(message.folderType);
boolean inOutbox = EntityFolder.OUTBOX.equals(message.folderType);
boolean inArchive = EntityFolder.ARCHIVE.equals(message.folderType);
//boolean inDafts = EntityFolder.DRAFTS.equals(message.folderType);
boolean inTrash = EntityFolder.TRASH.equals(message.folderType);
boolean inJunk = EntityFolder.JUNK.equals(message.folderType);
//boolean inSent = EntityFolder.SENT.equals(message.folderType);
boolean hasTrash = false;
boolean hasJunk = false;
@ -326,18 +324,20 @@ public class FragmentMessage extends FragmentEx {
hasUser = true;
}
bottom_navigation.setTag(inTrash || !hasTrash);
top_navigation.getMenu().findItem(R.id.action_thread).setVisible(message.count > 1);
top_navigation.getMenu().findItem(R.id.action_seen).setVisible(!outbox);
top_navigation.getMenu().findItem(R.id.action_edit).setVisible(trash);
top_navigation.getMenu().findItem(R.id.action_forward).setVisible(!outbox);
top_navigation.getMenu().findItem(R.id.action_reply_all).setVisible(!outbox && message.cc != null);
top_navigation.getMenu().findItem(R.id.action_seen).setVisible(!inOutbox);
top_navigation.getMenu().findItem(R.id.action_edit).setVisible(inTrash);
top_navigation.getMenu().findItem(R.id.action_forward).setVisible(!inOutbox);
top_navigation.getMenu().findItem(R.id.action_reply_all).setVisible(!inOutbox && message.cc != null);
top_navigation.setVisibility(View.VISIBLE);
bottom_navigation.getMenu().findItem(R.id.action_spam).setVisible(!outbox && !junk && hasJunk);
bottom_navigation.getMenu().findItem(R.id.action_trash).setVisible(!outbox && !trash && hasTrash);
bottom_navigation.getMenu().findItem(R.id.action_move).setVisible(!outbox && (!inbox || hasUser));
bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(!outbox && !archive && hasArchive);
bottom_navigation.getMenu().findItem(R.id.action_reply).setVisible(!outbox);
bottom_navigation.getMenu().findItem(R.id.action_spam).setVisible(!inOutbox && !inJunk && hasJunk);
bottom_navigation.getMenu().findItem(R.id.action_trash).setVisible(!inOutbox && hasTrash);
bottom_navigation.getMenu().findItem(R.id.action_move).setVisible(!inOutbox && (!inInbox || hasUser));
bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(!inOutbox && !inArchive && hasArchive);
bottom_navigation.getMenu().findItem(R.id.action_reply).setVisible(!inOutbox);
bottom_navigation.setVisibility(View.VISIBLE);
}
});
@ -383,44 +383,89 @@ public class FragmentMessage extends FragmentEx {
fragmentTransaction.commit();
}
private void onActionSeen(final long id) {
executor.submit(new Runnable() {
private void onActionSeen(long id) {
final MenuItem item = top_navigation.getMenu().findItem(R.id.action_seen);
item.setEnabled(false);
final Drawable icon = item.getIcon();
item.setIcon(Helper.toDimmed(icon));
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleLoader() {
@Override
public void run() {
public Object onLoad(Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(getContext());
try {
DB db = DB.getInstance(getContext());
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
message.ui_seen = !message.ui_seen;
db.message().updateMessage(message);
EntityOperation.queue(getContext(), message, EntityOperation.SEEN, message.ui_seen);
EntityOperation.process(getContext());
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
if (message.uid != null)
EntityOperation.queue(db, message, EntityOperation.SEEN, message.ui_seen);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(getContext());
return null;
}
});
@Override
public void onLoaded(Bundle args, Result result) {
item.setEnabled(true);
item.setIcon(icon);
if (result.ex != null)
Toast.makeText(getContext(), result.ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(this, ActivityView.LOADER_MESSAGE_SEEN, args);
}
private void onActionEdit(final long id) {
executor.submit(new Runnable() {
private void onActionEdit(long id) {
final MenuItem item = top_navigation.getMenu().findItem(R.id.action_edit);
item.setEnabled(false);
final Drawable icon = item.getIcon();
item.setIcon(Helper.toDimmed(icon));
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleLoader() {
@Override
public void run() {
try {
DB db = DB.getInstance(getContext());
EntityMessage draft = db.message().getMessage(id);
EntityFolder drafts = EntityFolder.getDrafts(getContext(), db, draft.account);
draft.id = null;
draft.folder = drafts.id;
draft.id = db.message().insertMessage(draft);
public Object onLoad(Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(getContext());
EntityMessage draft = db.message().getMessage(id);
EntityFolder drafts = EntityFolder.getDrafts(getContext(), db, draft.account);
draft.id = null;
draft.folder = drafts.id;
draft.uid = null;
draft.id = db.message().insertMessage(draft);
return id;
}
@Override
public void onLoaded(Bundle args, Result result) {
item.setEnabled(true);
item.setIcon(icon);
if (result.ex == null)
getContext().startActivity(
new Intent(getContext(), ActivityCompose.class)
.putExtra("id", draft.id));
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
.putExtra("id", (long) result.data));
else
Toast.makeText(getContext(), result.ex.toString(), Toast.LENGTH_LONG).show();
}
});
}.load(this, ActivityView.LOADER_MESSAGE_EDIT, args);
}
private void onActionForward(long id) {
@ -442,102 +487,231 @@ public class FragmentMessage extends FragmentEx {
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
executor.submit(new Runnable() {
final MenuItem item = bottom_navigation.getMenu().findItem(R.id.action_spam);
item.setEnabled(false);
final Drawable icon = item.getIcon();
item.setIcon(Helper.toDimmed(icon));
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleLoader() {
@Override
public void run() {
public Object onLoad(Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(getContext());
try {
DB db = DB.getInstance(getContext());
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
message.ui_hide = true;
EntityFolder spam = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, spam.id, message.uid);
message.folder = spam.id;
message.uid = null;
db.message().updateMessage(message);
EntityFolder spam = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, spam.id);
EntityOperation.process(getContext());
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
if (!move)
EntityOperation.queue(db, message, EntityOperation.ADD);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(getContext());
return null;
}
@Override
public void onLoaded(Bundle args, Result result) {
item.setEnabled(true);
item.setIcon(icon);
if (result.ex != null)
Toast.makeText(getContext(), result.ex.toString(), Toast.LENGTH_LONG).show();
}
});
}.load(FragmentMessage.this, ActivityView.LOADER_MESSAGE_SPAM, args);
}
})
.setNegativeButton(android.R.string.cancel, null).show();
}
private void onActionDelete(final long id) {
String folderType = (String) bottom_navigation.getTag();
if (EntityFolder.TRASH.equals(folderType)) {
boolean delete = (Boolean) bottom_navigation.getTag();
if (delete) {
// No trash or is trash
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder
.setMessage(R.string.title_ask_delete)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
executor.submit(new Runnable() {
final MenuItem item = bottom_navigation.getMenu().findItem(R.id.action_trash);
item.setEnabled(false);
final Drawable icon = item.getIcon();
item.setIcon(Helper.toDimmed(icon));
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleLoader() {
@Override
public void run() {
public Object onLoad(Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(getContext());
try {
DB db = DB.getInstance(getContext());
EntityMessage message = db.message().getMessage(id);
message.ui_hide = true;
db.message().updateMessage(message);
db.beginTransaction();
EntityOperation.queue(getContext(), message, EntityOperation.DELETE);
EntityOperation.process(getContext());
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
EntityMessage message = db.message().getMessage(id);
if (message.uid == null)
db.message().deleteMessage(id);
else {
message.ui_hide = true;
db.message().updateMessage(message);
EntityOperation.queue(db, message, EntityOperation.DELETE);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(getContext());
return null;
}
});
@Override
public void onLoaded(Bundle args, Result result) {
item.setEnabled(true);
item.setIcon(icon);
if (result.ex != null)
Toast.makeText(getContext(), result.ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(FragmentMessage.this, ActivityView.LOADER_MESSAGE_TRASH, args);
}
})
.setNegativeButton(android.R.string.cancel, null).show();
} else {
executor.submit(new Runnable() {
final MenuItem item = bottom_navigation.getMenu().findItem(R.id.action_trash);
item.setEnabled(false);
final Drawable icon = item.getIcon();
item.setIcon(Helper.toDimmed(icon));
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleLoader() {
@Override
public void run() {
public Object onLoad(Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(getContext());
try {
DB db = DB.getInstance(getContext());
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
message.ui_hide = true;
EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id, message.uid);
message.folder = trash.id;
message.uid = null;
db.message().updateMessage(message);
EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, trash.id);
EntityOperation.process(getContext());
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
if (!move)
EntityOperation.queue(db, message, EntityOperation.ADD);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(getContext());
return null;
}
});
@Override
public void onLoaded(Bundle args, Result result) {
item.setEnabled(true);
item.setIcon(icon);
if (result.ex != null)
Toast.makeText(getContext(), result.ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(FragmentMessage.this, ActivityView.LOADER_MESSAGE_TRASH, args);
}
}
private void onActionMove(final long id) {
private void onActionMove(long id) {
Bundle args = new Bundle();
args.putLong("id", id);
LoaderManager.getInstance(this)
.restartLoader(ActivityView.LOADER_MESSAGE_MOVE, args, moveLoaderCallbacks).forceLoad();
}
private void onActionArchive(final long id) {
executor.submit(new Runnable() {
private void onActionArchive(long id) {
final MenuItem item = bottom_navigation.getMenu().findItem(R.id.action_archive);
item.setEnabled(false);
final Drawable icon = item.getIcon();
item.setIcon(Helper.toDimmed(icon));
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleLoader() {
@Override
public void run() {
public Object onLoad(Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(getContext());
try {
DB db = DB.getInstance(getContext());
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
message.ui_hide = true;
EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, archive.id, message.uid);
message.folder = archive.id;
message.uid = null;
db.message().updateMessage(message);
EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, archive.id);
EntityOperation.process(getContext());
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
if (!move)
EntityOperation.queue(db, message, EntityOperation.ADD);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(getContext());
return null;
}
});
@Override
public void onLoaded(Bundle args, Result result) {
item.setEnabled(true);
item.setIcon(icon);
if (result.ex != null)
Toast.makeText(getContext(), result.ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(FragmentMessage.this, ActivityView.LOADER_MESSAGE_ARCHIVE, args);
}
private void onActionReply(long id) {
@ -603,7 +777,7 @@ public class FragmentMessage extends FragmentEx {
public void onLoadFinished(@NonNull Loader<List<EntityFolder>> loader, List<EntityFolder> folders) {
LoaderManager.getInstance(FragmentMessage.this).destroyLoader(loader.getId());
View anchor = top_navigation.findViewById(R.id.action_thread);
View anchor = bottom_navigation.findViewById(R.id.action_move);
PopupMenu popupMenu = new PopupMenu(getContext(), anchor);
int order = 0;
for (EntityFolder folder : folders)
@ -612,24 +786,56 @@ public class FragmentMessage extends FragmentEx {
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
final long folder = item.getItemId();
executor.submit(new Runnable() {
public boolean onMenuItemClick(final MenuItem target) {
final MenuItem item = bottom_navigation.getMenu().findItem(R.id.action_move);
item.setEnabled(false);
final Drawable icon = item.getIcon();
item.setIcon(Helper.toDimmed(icon));
args.putLong("target", target.getItemId());
new SimpleLoader() {
@Override
public void run() {
public Object onLoad(Bundle args) {
long id = args.getLong("id");
long target = args.getLong("target");
DB db = DB.getInstance(getContext());
try {
DB db = DB.getInstance(getContext());
EntityMessage message = db.message().getMessage(args.getLong("id"));
message.ui_hide = true;
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, target, message.uid);
message.folder = target;
message.uid = null;
db.message().updateMessage(message);
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, folder);
EntityOperation.process(getContext());
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
if (!move)
EntityOperation.queue(db, message, EntityOperation.ADD);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(getContext());
return null;
}
});
@Override
public void onLoaded(Bundle args, Result result) {
item.setEnabled(true);
item.setIcon(icon);
if (result.ex != null)
Toast.makeText(getContext(), result.ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(FragmentMessage.this, ActivityView.LOADER_MESSAGE_MOVE, args);
return true;
}

@ -21,6 +21,9 @@ package eu.faircode.email;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
@ -29,6 +32,8 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import androidx.annotation.NonNull;
public class Helper {
static final String TAG = BuildConfig.APPLICATION_ID;
@ -40,6 +45,16 @@ public class Helper {
return color;
}
static Drawable toDimmed(@NonNull Drawable drawable) {
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
Drawable mutated = drawable.mutate();
mutated.setColorFilter(filter);
mutated.setAlpha(128);
return mutated;
}
static String localizeFolderName(Context context, String name) {
if ("INBOX".equals(name))
return context.getString(R.string.title_folder_inbox);

@ -31,7 +31,7 @@ public class MimeMessageEx extends MimeMessage {
.append('>');
setHeader("Message-ID", sb.toString());
Log.i(Helper.TAG, "Override Message-ID=" + sb.toString());
Log.v(Helper.TAG, "Override Message-ID=" + sb.toString());
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
super.updateMessageID();
@ -54,7 +54,7 @@ public class MimeMessageEx extends MimeMessage {
return -1;
long id = Long.parseLong(part.substring(1));
Log.i(Helper.TAG, "Parsed Message-ID=" + msgid + " id=" + id);
Log.v(Helper.TAG, "Parsed Message-ID=" + msgid + " id=" + id);
return id;
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));

@ -596,8 +596,8 @@ public class ServiceSynchronize extends LifecycleService {
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);
long uid = ifolder.getUID(imessage);
int count = db.message().deleteMessage(folder.id, uid);
Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count);
} catch (MessageRemovedException ex) {
@ -694,215 +694,56 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, folder.name + " start process");
DB db = DB.getInstance(this);
DaoOperation operation = db.operation();
DaoMessage message = db.message();
for (TupleOperationEx op : operation.getOperations(folder.id))
for (EntityOperation op : db.operation().getOperations(folder.id))
try {
Log.i(Helper.TAG, folder.name +
" start op=" + op.id + "/" + op.name +
" args=" + op.args +
" msg=" + op.message);
" msg=" + op.message +
" args=" + op.args);
JSONArray jargs = new JSONArray(op.args);
EntityMessage message = db.message().getMessage(op.message);
try {
if (EntityOperation.SEEN.equals(op.name)) {
// Mark message (un)seen
Message imessage = ifolder.getMessageByUID(op.uid);
if (imessage == null)
throw new MessageRemovedException();
imessage.setFlag(Flags.Flag.SEEN, jargs.getBoolean(0));
} else if (EntityOperation.ADD.equals(op.name)) {
// Append message
EntityMessage msg = message.getMessage(op.message);
if (msg == null)
return;
// Disconnect from remote to prevent deletion
Long uid = msg.uid;
if (msg.uid != null) {
msg.uid = null;
message.updateMessage(msg);
}
// Execute append
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
MimeMessage imessage = MessageHelper.from(msg, isession);
ifolder.appendMessages(new Message[]{imessage});
// Drafts can be appended multiple times
if (uid != null) {
Message previously = ifolder.getMessageByUID(uid);
if (previously == null)
throw new MessageRemovedException();
previously.setFlag(Flags.Flag.DELETED, true);
ifolder.expunge();
}
} else if (EntityOperation.MOVE.equals(op.name)) {
EntityFolder target = db.folder().getFolder(jargs.getLong(0));
if (target == null)
throw new FolderNotFoundException();
// Move message
Message imessage = ifolder.getMessageByUID(op.uid);
if (imessage == null)
throw new MessageRemovedException();
Folder itarget = istore.getFolder(target.name);
if (istore.hasCapability("MOVE"))
ifolder.moveMessages(new Message[]{imessage}, itarget);
else {
Log.i(Helper.TAG, "MOVE by APPEND/DELETE");
EntityMessage msg = message.getMessage(op.message);
// Execute append
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
MimeMessage icopy = MessageHelper.from(msg, isession);
itarget.appendMessages(new Message[]{icopy});
// Execute delete
imessage.setFlag(Flags.Flag.DELETED, true);
ifolder.expunge();
}
message.deleteMessage(op.message);
} else if (EntityOperation.DELETE.equals(op.name)) {
// Delete message
if (op.uid != null) {
Message imessage = ifolder.getMessageByUID(op.uid);
if (imessage == null)
throw new MessageRemovedException();
imessage.setFlag(Flags.Flag.DELETED, true);
ifolder.expunge();
}
message.deleteMessage(op.message);
if (EntityOperation.SEEN.equals(op.name))
doSeen(ifolder, jargs, message);
} else if (EntityOperation.SEND.equals(op.name)) {
// Send message
EntityMessage msg = message.getMessage(op.message);
if (msg == null)
return;
else if (EntityOperation.ADD.equals(op.name))
doAdd(ifolder, message);
EntityMessage reply = (msg.replying == null ? null : message.getMessage(msg.replying));
EntityIdentity ident = db.identity().getIdentity(msg.identity);
else if (EntityOperation.MOVE.equals(op.name))
doMove(folder, istore, ifolder, db, jargs, message);
if (ident == null || !ident.synchronize) {
// Message will remain in outbox
return;
}
// Create session
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
// Create message
MimeMessage imessage;
if (reply == null)
imessage = MessageHelper.from(msg, isession);
else
imessage = MessageHelper.from(msg, reply, isession);
if (ident.replyto != null)
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});
// Create transport
Transport itransport = isession.getTransport(ident.starttls ? "smtp" : "smtps");
try {
// Connect transport
itransport.connect(ident.host, ident.port, ident.user, ident.password);
// Send message
Address[] to = imessage.getAllRecipients();
itransport.sendMessage(imessage, to);
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
" to " + TextUtils.join(", ", to));
msg.sent = new Date().getTime();
msg.seen = true;
msg.ui_seen = true;
EntityFolder sent = db.folder().getFolderByType(ident.account, EntityFolder.SENT);
if (sent != null) {
Log.i(Helper.TAG, "Moving to sent folder=" + sent.id);
msg.folder = sent.id;
}
else if (EntityOperation.DELETE.equals(op.name))
doDelete(folder, ifolder, db, message);
message.updateMessage(msg);
else if (EntityOperation.SEND.equals(op.name))
doSend(db, message);
} finally {
itransport.close();
}
// TODO: cache transport?
} else if (EntityOperation.ATTACHMENT.equals(op.name)) {
int sequence = jargs.getInt(0);
EntityAttachment attachment = db.attachment().getAttachment(op.message, sequence);
if (attachment == null)
return;
try {
// Get message
Message imessage = ifolder.getMessageByUID(op.uid);
if (imessage == null)
throw new MessageRemovedException();
// Get attachment
MessageHelper helper = new MessageHelper((MimeMessage) imessage);
EntityAttachment a = helper.getAttachments().get(sequence - 1);
// Download attachment
InputStream is = a.part.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[DOWNLOAD_BUFFER_SIZE];
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
os.write(buffer, 0, len);
// Update progress
if (attachment.size != null) {
attachment.progress = os.size() * 100 / attachment.size;
db.attachment().updateAttachment(attachment);
Log.i(Helper.TAG, "Progress %=" + attachment.progress);
}
}
else if (EntityOperation.ATTACHMENT.equals(op.name))
doAttachment(ifolder, db, op, jargs, message);
// Store attachment data
attachment.progress = null;
attachment.content = os.toByteArray();
db.attachment().updateAttachment(attachment);
Log.i(Helper.TAG, "Downloaded bytes=" + attachment.content.length);
} catch (Throwable ex) {
// Reset progress on failure
attachment.progress = null;
db.attachment().updateAttachment(attachment);
throw ex;
}
} else
else
throw new MessagingException("Unknown operation name=" + op.name);
// Operation succeeded
operation.deleteOperation(op.id);
db.operation().deleteOperation(op.id);
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
// There is no use in repeating
operation.deleteOperation(op.id);
db.operation().deleteOperation(op.id);
} catch (FolderNotFoundException ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
// There is no use in repeating
operation.deleteOperation(op.id);
db.operation().deleteOperation(op.id);
} catch (SMTPSendFailedException ex) {
// TODO: response codes: https://www.ietf.org/rfc/rfc821.txt
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
// There is probably no use in repeating
operation.deleteOperation(op.id);
db.operation().deleteOperation(op.id);
throw ex;
} catch (MessagingException ex) {
// Socket timeout is a recoverable condition (send message)
@ -921,6 +762,207 @@ public class ServiceSynchronize extends LifecycleService {
}
}
private void doSeen(IMAPFolder ifolder, JSONArray jargs, EntityMessage message) throws MessagingException, JSONException {
if (message.uid == null)
return;
// Mark message (un)seen
Message imessage = ifolder.getMessageByUID(message.uid);
if (imessage == null)
throw new MessageRemovedException();
imessage.setFlag(Flags.Flag.SEEN, jargs.getBoolean(0));
}
private void doAdd(IMAPFolder ifolder, EntityMessage message) throws MessagingException {
// Append message
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
MimeMessage imessage = MessageHelper.from(message, isession);
ifolder.appendMessages(new Message[]{imessage});
}
private void doMove(EntityFolder source, IMAPStore istore, IMAPFolder ifolder, DB db, JSONArray jargs, EntityMessage message) throws JSONException, MessagingException {
long id = jargs.getLong(0);
long uid = jargs.getLong(1);
EntityFolder target = db.folder().getFolder(id);
if (target == null)
throw new FolderNotFoundException();
// Get message
Message imessage = ifolder.getMessageByUID(uid);
if (imessage == null)
throw new MessageRemovedException();
// Get folder
Folder itarget = istore.getFolder(target.name);
// Append/delete because message ID header needs to be added and not all providers support MOVE
long oid = message.id;
try {
db.beginTransaction();
// Hide original (to be deleted)
message.ui_hide = true;
db.message().updateMessage(message);
// Copy message (to be appended)
if (!EntityFolder.ARCHIVE.equals(target.type)) {
message.id = null;
message.ui_hide = false;
message.id = db.message().insertMessage(message);
// New archived message will be created
EntityMessage archived = db.message().getArchivedMessage(message.thread);
if (archived != null)
db.message().deleteMessage(archived.id);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
// Append copy
if (!EntityFolder.ARCHIVE.equals(target.type)) {
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
MimeMessage icopy = MessageHelper.from(message, isession);
itarget.appendMessages(new Message[]{icopy});
icopy.setFlag(Flags.Flag.SEEN, message.seen);
}
// Delete original
imessage.setFlag(Flags.Flag.DELETED, true);
ifolder.expunge();
db.message().deleteMessage(oid);
}
private void doDelete(EntityFolder folder, IMAPFolder ifolder, DB db, EntityMessage message) throws MessagingException {
// Delete message
Message imessage = ifolder.getMessageByUID(message.uid);
if (imessage == null)
throw new MessageRemovedException();
imessage.setFlag(Flags.Flag.DELETED, true);
ifolder.expunge();
db.message().deleteMessage(message.id);
}
private void doSend(DB db, EntityMessage message) throws MessagingException {
// Send message
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
EntityIdentity ident = db.identity().getIdentity(message.identity);
if (!ident.synchronize) {
// Message will remain in outbox
return;
}
try {
db.beginTransaction();
// Move message to sent
EntityFolder sent = db.folder().getFolderByType(ident.account, EntityFolder.SENT);
if (sent == null)
; // Leave message in outbox
else {
message.folder = sent.id;
message.uid = null;
}
// Create session
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
// Create message
MimeMessage imessage;
if (reply == null)
imessage = MessageHelper.from(message, isession);
else
imessage = MessageHelper.from(message, reply, isession);
if (ident.replyto != null)
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});
// Create transport
// TODO: cache transport?
Transport itransport = isession.getTransport(ident.starttls ? "smtp" : "smtps");
try {
// Connect transport
itransport.connect(ident.host, ident.port, ident.user, ident.password);
// Send message
Address[] to = imessage.getAllRecipients();
itransport.sendMessage(imessage, to);
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
" to " + TextUtils.join(", ", to));
// Update state
message.sent = new Date().getTime();
message.seen = true;
message.ui_seen = true;
db.message().updateMessage(message);
if (sent != null)
EntityOperation.queue(db, message, EntityOperation.ADD); // Could already exist
} finally {
itransport.close();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private void doAttachment(IMAPFolder ifolder, DB db, EntityOperation op, JSONArray jargs, EntityMessage message) throws JSONException, MessagingException, IOException {
int sequence = jargs.getInt(0);
EntityAttachment attachment = db.attachment().getAttachment(op.message, sequence);
if (attachment == null)
return;
try {
// Get message
Message imessage = ifolder.getMessageByUID(message.uid);
if (imessage == null)
throw new MessageRemovedException();
// Get attachment
MessageHelper helper = new MessageHelper((MimeMessage) imessage);
EntityAttachment a = helper.getAttachments().get(sequence - 1);
// Download attachment
InputStream is = a.part.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[DOWNLOAD_BUFFER_SIZE];
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
os.write(buffer, 0, len);
// Update progress
if (attachment.size != null) {
attachment.progress = os.size() * 100 / attachment.size;
db.attachment().updateAttachment(attachment);
Log.i(Helper.TAG, "Progress %=" + attachment.progress);
}
}
// Store attachment data
attachment.progress = null;
attachment.content = os.toByteArray();
db.attachment().updateAttachment(attachment);
Log.i(Helper.TAG, "Downloaded bytes=" + attachment.content.length);
} catch (Throwable ex) {
// Reset progress on failure
attachment.progress = null;
db.attachment().updateAttachment(attachment);
throw ex;
}
}
private void synchronizeFolders(EntityAccount account, IMAPStore istore) throws MessagingException {
try {
Log.i(Helper.TAG, "Start sync folders");
@ -1029,6 +1071,9 @@ public class ServiceSynchronize extends LifecycleService {
}
// Add/update local messages
int added = 0;
int updated = 0;
int unchanged = 0;
Log.i(Helper.TAG, folder.name + " add=" + imessages.length);
for (int batch = 0; batch < imessages.length; batch += FETCH_BATCH_SIZE) {
Log.i(Helper.TAG, folder.name + " fetch @" + batch);
@ -1036,7 +1081,13 @@ public class ServiceSynchronize extends LifecycleService {
db.beginTransaction();
for (int i = 0; i < FETCH_BATCH_SIZE && batch + i < imessages.length; i++)
try {
synchronizeMessage(folder, ifolder, (IMAPMessage) imessages[batch + i]);
int status = synchronizeMessage(folder, ifolder, (IMAPMessage) imessages[batch + i]);
if (status > 0)
added++;
else if (status < 0)
updated++;
else
unchanged++;
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
@ -1045,12 +1096,13 @@ public class ServiceSynchronize extends LifecycleService {
db.endTransaction();
}
}
Log.i(Helper.TAG, folder.name + " added=" + added + " updated=" + updated + " unchanged=" + unchanged);
} finally {
Log.i(Helper.TAG, folder.name + " end sync");
}
}
private void synchronizeMessage(EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage) throws MessagingException, JSONException, IOException {
private int synchronizeMessage(EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage) throws MessagingException, JSONException, IOException {
long uid = -1;
try {
FetchProfile fp = new FetchProfile();
@ -1063,18 +1115,36 @@ public class ServiceSynchronize extends LifecycleService {
if (imessage.isExpunged()) {
Log.i(Helper.TAG, folder.name + " expunged uid=" + uid);
return;
return 0;
}
if (imessage.isSet(Flags.Flag.DELETED)) {
Log.i(Helper.TAG, folder.name + " deleted uid=" + uid);
return;
return 0;
}
DB db = DB.getInstance(this);
EntityMessage message = null;
long id = MimeMessageEx.getId(imessage);
if (id >= 0) {
message = db.message().getMessage(id);
Log.i(Helper.TAG, "By id=" + id + " uid=" + (message == null ? "n/a" : message.uid));
}
if (message != null && message.folder != folder.id) {
if (EntityFolder.ARCHIVE.equals(folder.type))
message = null;
else // Outbox to sent
message.folder = folder.id;
}
if (message == null) {
message = db.message().getMessage(folder.id, uid);
Log.i(Helper.TAG, "By uid=" + uid + " id=" + (message == null ? "n/a" : message.id));
}
MessageHelper helper = new MessageHelper(imessage);
boolean seen = helper.getSeen();
DB db = DB.getInstance(this);
EntityMessage message = db.message().getMessage(folder.id, uid);
if (message == null) {
FetchProfile fp1 = new FetchProfile();
fp1.add(FetchProfile.Item.ENVELOPE);
@ -1083,18 +1153,7 @@ public class ServiceSynchronize extends LifecycleService {
fp1.add(IMAPFolder.FetchProfileItem.MESSAGE);
ifolder.fetch(new Message[]{imessage}, fp1);
long id = MimeMessageEx.getId(imessage);
message = db.message().getMessage(id);
if (message != null && message.folder != folder.id) {
if (EntityFolder.ARCHIVE.equals(folder.type))
message = null;
else // Outbox to sent
message.folder = folder.id;
}
boolean update = (message != null);
if (message == null)
message = new EntityMessage();
message = new EntityMessage();
message.account = folder.account;
message.folder = folder.id;
message.uid = uid;
@ -1115,13 +1174,8 @@ public class ServiceSynchronize extends LifecycleService {
message.ui_seen = seen;
message.ui_hide = false;
if (update) {
db.message().updateMessage(message);
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid);
} else {
message.id = db.message().insertMessage(message);
Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid);
}
message.id = db.message().insertMessage(message);
Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid);
int sequence = 0;
for (EntityAttachment attachment : helper.getAttachments()) {
@ -1132,14 +1186,26 @@ public class ServiceSynchronize extends LifecycleService {
attachment.sequence = sequence;
attachment.id = db.attachment().insertAttachment(attachment);
}
return 1;
} else if (message.seen != seen) {
//if (message.uid == null)
message.uid = uid; // Complete move
message.seen = seen;
message.ui_seen = seen;
// TODO: synchronize all data?
db.message().updateMessage(message);
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid);
return -1;
} else {
Log.v(Helper.TAG, folder.name + " unchanged id=" + message.id + " uid=" + message.uid);
if (message.uid == null) {
message.uid = uid;
db.message().updateMessage(message);
Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " set uid=" + message.uid);
return -1;
} else {
Log.v(Helper.TAG, folder.name + " unchanged id=" + message.id + " uid=" + message.uid);
return 0;
}
}
} finally {

@ -0,0 +1,92 @@
package eu.faircode.email;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;
public abstract class SimpleLoader {
private Context context;
private LoaderManager manager;
public void load(AppCompatActivity activity, int id, Bundle args) {
this.context = activity;
this.manager = LoaderManager.getInstance(activity);
manager.restartLoader(id, args, callbacks).forceLoad();
}
public void load(Fragment fragment, int id, Bundle args) {
this.context = fragment.getContext();
this.manager = LoaderManager.getInstance(fragment);
manager.restartLoader(id, args, callbacks).forceLoad();
}
public Object onLoad(Bundle args) {
return null;
}
public void onLoaded(Bundle args, Result result) {
}
private static class CommonLoader extends AsyncTaskLoader<Result> {
Bundle args;
SimpleLoader loader;
CommonLoader(Context context) {
super(context);
}
void setArgs(Bundle args, SimpleLoader x) {
this.args = args;
this.loader = x;
}
@Override
public Result loadInBackground() {
Result result = new Result();
try {
result.data = loader.onLoad(args);
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
result.ex = ex;
}
return result;
}
}
private LoaderManager.LoaderCallbacks callbacks = new LoaderManager.LoaderCallbacks<Result>() {
@Override
public Loader<Result> onCreateLoader(int id, Bundle args) {
CommonLoader loader = new CommonLoader(context);
loader.setArgs(args, SimpleLoader.this);
return loader;
}
@Override
public void onLoadFinished(Loader<Result> loader, Result data) {
manager.destroyLoader(loader.getId());
CommonLoader common = (CommonLoader) loader;
onLoaded(common.args, data);
common.args = null;
common.loader = null;
manager = null;
}
@Override
public void onLoaderReset(Loader<Result> loader) {
}
};
public static class Result {
Throwable ex;
Object data;
}
}

@ -1,24 +0,0 @@
package eu.faircode.email;
/*
This file is part of Safe email.
Safe email 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.
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018 by Marcel Bokhorst (M66B)
*/
public class TupleOperationEx extends EntityOperation {
public Long uid;
}
Loading…
Cancel
Save