Get attachment meta info

pull/12/merge
M66B 6 years ago
parent 485ef3ff56
commit bb4bed926a

@ -9,6 +9,12 @@ android {
versionCode 3
versionName "0.3"
archivesBaseName = "SafeEmail-v$versionName"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildTypes {

@ -0,0 +1,639 @@
{
"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\")"
]
}
}

@ -37,9 +37,10 @@ import android.util.Log;
EntityAccount.class,
EntityFolder.class,
EntityMessage.class,
EntityAttachment.class,
EntityOperation.class
},
version = 1,
version = 2,
exportSchema = true
)
@ -53,6 +54,8 @@ public abstract class DB extends RoomDatabase {
public abstract DaoMessage message();
public abstract DaoAttachment attachment();
public abstract DaoOperation operation();
private static DB sInstance;
@ -67,7 +70,7 @@ public abstract class DB extends RoomDatabase {
private static DB migrate(RoomDatabase.Builder<DB> builder) {
return builder
//.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_1_2)
.build();
}
@ -75,7 +78,14 @@ public abstract class DB extends RoomDatabase {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE message ADD COLUMN error TEXT");
db.execSQL("CREATE TABLE IF NOT EXISTS `attachment`" +
" (`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 )");
db.execSQL("CREATE INDEX `index_attachment_message` ON `attachment` (`message`)");
db.execSQL("CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `attachment` (`message`, `sequence`)");
}
};

@ -0,0 +1,30 @@
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)
*/
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
@Dao
public interface DaoAttachment {
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertAttachment(EntityAttachment attachment);
}

@ -33,7 +33,8 @@ import static android.arch.persistence.room.ForeignKey.CASCADE;
@ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
},
indices = {
@Index(value = {"message"})
@Index(value = {"message"}),
@Index(value = {"message", "sequence"}, unique = true)
}
)
public class EntityAttachment {
@ -44,7 +45,9 @@ public class EntityAttachment {
@NonNull
public Long message;
@NonNull
public String type;
public Integer sequence;
public String name;
@NonNull
public String type;
public byte[] content;
}

@ -37,12 +37,14 @@ import java.util.List;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
@ -123,6 +125,10 @@ public class MessageHelper {
this.imessage = new MimeMessage(isession, is);
}
boolean getSeen() throws MessagingException {
return imessage.isSet(Flags.Flag.SEEN);
}
String getMessageID() throws MessagingException {
return imessage.getHeader("Message-ID", null);
}
@ -205,10 +211,6 @@ public class MessageHelper {
return result.toArray(new Address[0]);
}
String getHtml() throws MessagingException {
return getHtml(imessage);
}
static String getFormattedAddresses(String json) {
if (json == null)
return null;
@ -230,6 +232,10 @@ public class MessageHelper {
}
}
String getHtml() throws MessagingException {
return getHtml(imessage);
}
private String getHtml(Part part) throws MessagingException {
if (part.isMimeType("text/*"))
try {
@ -280,8 +286,42 @@ public class MessageHelper {
return null;
}
boolean getSeen() throws MessagingException {
return imessage.isSet(Flags.Flag.SEEN);
public List<EntityAttachment> getAttachments() throws IOException, MessagingException {
List<EntityAttachment> result = new ArrayList<>();
Object content = imessage.getContent();
if (content instanceof String)
return result;
if (content instanceof Multipart) {
Multipart multipart = (Multipart) content;
for (int i = 0; i < multipart.getCount(); i++)
result.addAll(getAttachments(multipart.getBodyPart(i)));
}
return result;
}
private List<EntityAttachment> getAttachments(BodyPart part) throws IOException, MessagingException {
List<EntityAttachment> result = new ArrayList<>();
Object content = part.getContent();
if (content instanceof InputStream || content instanceof String) {
if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) || !TextUtils.isEmpty(part.getFileName())) {
ContentType ct = new ContentType(part.getContentType());
EntityAttachment attachment = new EntityAttachment();
attachment.sequence = result.size() + 1;
attachment.name = part.getFileName();
attachment.type = ct.getBaseType();
result.add(attachment);
}
} else if (content instanceof Multipart) {
Multipart multipart = (Multipart) content;
for (int i = 0; i < multipart.getCount(); i++)
result.addAll(getAttachments(multipart.getBodyPart(i)));
}
return result;
}
String getRaw() throws IOException, MessagingException {

@ -50,6 +50,7 @@ import com.sun.mail.imap.protocol.IMAPProtocol;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@ -101,7 +102,10 @@ public class ServiceSynchronize extends LifecycleService {
}
public ServiceSynchronize() {
// https://docs.oracle.com/javaee/6/api/javax/mail/internet/package-summary.html
System.setProperty("mail.mime.ignoreunknownencoding", "true");
System.setProperty("mail.mime.decodefilename", "true");
System.setProperty("mail.mime.encodefilename", "true");
}
@Override
@ -383,7 +387,7 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, account.name + " stopped");
}
private void monitorFolder(final EntityAccount account, final EntityFolder folder, final IMAPStore istore) throws MessagingException, JSONException {
private void monitorFolder(final EntityAccount account, final EntityFolder folder, final IMAPStore istore) throws MessagingException, JSONException, IOException {
IMAPFolder ifolder = null;
try {
Log.i(Helper.TAG, folder.name + " start");
@ -722,7 +726,7 @@ public class ServiceSynchronize extends LifecycleService {
}
}
private void synchronizeMessages(EntityFolder folder, IMAPFolder ifolder) throws MessagingException, JSONException {
private void synchronizeMessages(EntityFolder folder, IMAPFolder ifolder) throws MessagingException, JSONException, IOException {
try {
Log.i(Helper.TAG, folder.name + " start sync after=" + folder.after);
@ -801,7 +805,7 @@ public class ServiceSynchronize extends LifecycleService {
}
}
private void synchronizeMessage(EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage) throws MessagingException, JSONException {
private void synchronizeMessage(EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage) throws MessagingException, JSONException, IOException {
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.FLAGS);
@ -856,6 +860,13 @@ public class ServiceSynchronize extends LifecycleService {
message.id = db.message().insertMessage(message);
Log.i(Helper.TAG, folder.name + " added id=" + message.id);
for (EntityAttachment attachment : helper.getAttachments()) {
Log.i(Helper.TAG, "attachment name=" + attachment.name + " type=" + attachment.type);
attachment.message = message.id;
db.attachment().insertAttachment(attachment);
}
} else if (message.seen != seen) {
message.seen = seen;
message.ui_seen = seen;

Loading…
Cancel
Save