Report/show account, folder, message and operation errors

pull/50/head
M66B 6 years ago
parent f83a4f60ec
commit 6713134e43

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "959322c3e61ed9307a830e355bb2b8ab", "identityHash": "9fd2cb9e7b45bf1dbddced278a737dfa",
"entities": [ "entities": [
{ {
"tableName": "identity", "tableName": "identity",
@ -113,7 +113,7 @@
}, },
{ {
"tableName": "account", "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)", "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, `error` TEXT)",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -168,6 +168,12 @@
"columnName": "seen_until", "columnName": "seen_until",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": false "notNull": false
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -181,7 +187,7 @@
}, },
{ {
"tableName": "folder", "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 )", "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, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -218,6 +224,12 @@
"columnName": "after", "columnName": "after",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -277,7 +289,7 @@
}, },
{ {
"tableName": "message", "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 )", "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, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -410,6 +422,12 @@
"columnName": "ui_hide", "columnName": "ui_hide",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -634,7 +652,7 @@
}, },
{ {
"tableName": "operation", "tableName": "operation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT, 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 )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `error` TEXT, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -664,6 +682,12 @@
"fieldPath": "args", "fieldPath": "args",
"columnName": "args", "columnName": "args",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": false "notNull": false
} }
], ],
@ -675,12 +699,12 @@
}, },
"indices": [ "indices": [
{ {
"name": "index_operation_message", "name": "index_operation_folder",
"unique": false, "unique": false,
"columnNames": [ "columnNames": [
"message" "folder"
], ],
"createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)" "createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
}, },
{ {
"name": "index_operation_message", "name": "index_operation_message",
@ -719,7 +743,7 @@
], ],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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, \"959322c3e61ed9307a830e355bb2b8ab\")" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"9fd2cb9e7b45bf1dbddced278a737dfa\")"
] ]
} }
} }

@ -54,6 +54,7 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
ImageView ivSync; ImageView ivSync;
TextView tvHost; TextView tvHost;
TextView tvUser; TextView tvUser;
TextView tvError;
ViewHolder(View itemView) { ViewHolder(View itemView) {
super(itemView); super(itemView);
@ -64,6 +65,7 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
ivSync = itemView.findViewById(R.id.ivSync); ivSync = itemView.findViewById(R.id.ivSync);
tvHost = itemView.findViewById(R.id.tvHost); tvHost = itemView.findViewById(R.id.tvHost);
tvUser = itemView.findViewById(R.id.tvUser); tvUser = itemView.findViewById(R.id.tvUser);
tvError = itemView.findViewById(R.id.tvError);
} }
private void wire() { private void wire() {
@ -80,6 +82,8 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
ivSync.setVisibility(account.synchronize ? View.VISIBLE : View.INVISIBLE); ivSync.setVisibility(account.synchronize ? View.VISIBLE : View.INVISIBLE);
tvHost.setText(String.format("%s:%d", account.host, account.port)); tvHost.setText(String.format("%s:%d", account.host, account.port));
tvUser.setText(account.user); tvUser.setText(account.user);
tvError.setText(account.error);
tvError.setVisibility(account.error == null ? View.GONE : View.VISIBLE);
} }
@Override @Override

@ -56,6 +56,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
TextView tvType; TextView tvType;
TextView tvAfter; TextView tvAfter;
ImageView ivSync; ImageView ivSync;
TextView tvError;
ViewHolder(View itemView) { ViewHolder(View itemView) {
super(itemView); super(itemView);
@ -66,6 +67,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
tvType = itemView.findViewById(R.id.tvType); tvType = itemView.findViewById(R.id.tvType);
tvAfter = itemView.findViewById(R.id.tvAfter); tvAfter = itemView.findViewById(R.id.tvAfter);
ivSync = itemView.findViewById(R.id.ivSync); ivSync = itemView.findViewById(R.id.ivSync);
tvError = itemView.findViewById(R.id.tvError);
} }
private void wire(boolean properties) { private void wire(boolean properties) {
@ -100,6 +102,9 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
tvAfter.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE); tvAfter.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE);
ivSync.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE); ivSync.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE);
tvError.setText(folder.error);
tvError.setVisibility(folder.error == null ? View.GONE : View.VISIBLE);
} }
@Override @Override

@ -52,6 +52,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
ImageView ivAttachments; ImageView ivAttachments;
TextView tvSubject; TextView tvSubject;
TextView tvCount; TextView tvCount;
TextView tvError;
ProgressBar pbLoading; ProgressBar pbLoading;
ViewHolder(View itemView) { ViewHolder(View itemView) {
@ -63,6 +64,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
ivAttachments = itemView.findViewById(R.id.ivAttachments); ivAttachments = itemView.findViewById(R.id.ivAttachments);
tvSubject = itemView.findViewById(R.id.tvSubject); tvSubject = itemView.findViewById(R.id.tvSubject);
tvCount = itemView.findViewById(R.id.tvCount); tvCount = itemView.findViewById(R.id.tvCount);
tvError = itemView.findViewById(R.id.tvError);
pbLoading = itemView.findViewById(R.id.pbLoading); pbLoading = itemView.findViewById(R.id.pbLoading);
} }
@ -98,6 +100,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
tvTime.setText(DateUtils.getRelativeTimeSpanString(context, message.received)); tvTime.setText(DateUtils.getRelativeTimeSpanString(context, message.received));
} }
ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE);
tvSubject.setText(message.subject); tvSubject.setText(message.subject);
String extra = (debug ? (message.ui_hide ? "HIDDEN " : "") + message.uid + "/" + message.id + " " : ""); String extra = (debug ? (message.ui_hide ? "HIDDEN " : "") + message.uid + "/" + message.id + " " : "");
@ -109,7 +112,8 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
tvCount.setVisibility(View.VISIBLE); tvCount.setVisibility(View.VISIBLE);
} }
ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE); tvError.setText(message.error);
tvError.setVisibility(message.error == null ? View.GONE : View.VISIBLE);
int typeface = (message.unseen > 0 ? Typeface.BOLD : Typeface.NORMAL); int typeface = (message.unseen > 0 ? Typeface.BOLD : Typeface.NORMAL);
tvFrom.setTypeface(null, typeface); tvFrom.setTypeface(null, typeface);

@ -25,6 +25,7 @@ import androidx.room.Dao;
import androidx.room.Insert; import androidx.room.Insert;
import androidx.room.OnConflictStrategy; import androidx.room.OnConflictStrategy;
import androidx.room.Query; import androidx.room.Query;
import androidx.room.Update;
@Dao @Dao
public interface DaoOperation { public interface DaoOperation {
@ -37,6 +38,9 @@ public interface DaoOperation {
@Query("SELECT COUNT(id) FROM operation WHERE folder = :folder") @Query("SELECT COUNT(id) FROM operation WHERE folder = :folder")
int getOperationCount(long folder); int getOperationCount(long folder);
@Update
void updateOperation(EntityOperation operation);
@Query("DELETE FROM operation WHERE id = :id") @Query("DELETE FROM operation WHERE id = :id")
void deleteOperation(long id); void deleteOperation(long id);

@ -47,6 +47,7 @@ public class EntityAccount {
@NonNull @NonNull
public Boolean synchronize; public Boolean synchronize;
public Long seen_until; public Long seen_until;
public String error;
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
@ -58,7 +59,8 @@ public class EntityAccount {
this.user.equals(other.user) && this.user.equals(other.user) &&
this.password.equals(other.password) && this.password.equals(other.password) &&
this.primary.equals(other.primary) && this.primary.equals(other.primary) &&
this.synchronize.equals(other.synchronize)); this.synchronize.equals(other.synchronize) &&
(this.error == null ? other.error == null : this.error.equals(other.error)));
} else } else
return false; return false;
} }

@ -46,6 +46,19 @@ import static androidx.room.ForeignKey.CASCADE;
public class EntityFolder implements Serializable { public class EntityFolder implements Serializable {
static final String TABLE_NAME = "folder"; static final String TABLE_NAME = "folder";
@PrimaryKey(autoGenerate = true)
public Long id;
public Long account; // Outbox = null
@NonNull
public String name;
@NonNull
public String type;
@NonNull
public Boolean synchronize;
@NonNull
public Integer after; // days
public String error;
static final String INBOX = "Inbox"; static final String INBOX = "Inbox";
static final String OUTBOX = "Outbox"; static final String OUTBOX = "Outbox";
static final String ARCHIVE = "All"; static final String ARCHIVE = "All";
@ -92,18 +105,6 @@ public class EntityFolder implements Serializable {
SENT SENT
); );
@PrimaryKey(autoGenerate = true)
public Long id;
public Long account; // Outbox = null
@NonNull
public String name;
@NonNull
public String type;
@NonNull
public Boolean synchronize;
@NonNull
public Integer after; // days
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof EntityFolder) { if (obj instanceof EntityFolder) {
@ -112,7 +113,8 @@ public class EntityFolder implements Serializable {
this.name.equals(other.name) && this.name.equals(other.name) &&
this.type.equals(other.type) && this.type.equals(other.type) &&
this.synchronize.equals(other.synchronize) && this.synchronize.equals(other.synchronize) &&
this.after.equals(other.after)); this.after.equals(other.after) &&
(this.error == null ? other.error == null : this.error.equals(other.error)));
} else } else
return false; return false;
} }

@ -82,6 +82,7 @@ public class EntityMessage {
public Boolean ui_seen; public Boolean ui_seen;
@NonNull @NonNull
public Boolean ui_hide; public Boolean ui_hide;
public String error;
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
@ -107,7 +108,8 @@ public class EntityMessage {
this.received.equals(other.received) && this.received.equals(other.received) &&
this.seen.equals(other.seen) && this.seen.equals(other.seen) &&
this.ui_seen.equals(other.ui_seen) && this.ui_seen.equals(other.ui_seen) &&
this.ui_hide.equals(other.ui_hide)); this.ui_hide.equals(other.ui_hide) &&
(this.error == null ? other.error == null : this.error.equals(other.error)));
} }
return false; return false;
} }

@ -59,7 +59,9 @@ public class EntityOperation {
public Long message; public Long message;
@NonNull @NonNull
public String name; public String name;
@NonNull
public String args; public String args;
public String error;
public static final String SEEN = "seen"; public static final String SEEN = "seen";
public static final String ADD = "add"; public static final String ADD = "add";
@ -123,4 +125,17 @@ public class EntityOperation {
queue.clear(); queue.clear();
} }
} }
@Override
public boolean equals(Object obj) {
if (obj instanceof EntityOperation) {
EntityOperation other = (EntityOperation) obj;
return (this.folder.equals(other.folder) &&
this.message.equals(other.message) &&
this.name.equals(other.name) &&
this.args.equals(other.args) &&
(this.error == null ? other.error == null : this.error.equals(other.error)));
} else
return false;
}
} }

@ -344,23 +344,26 @@ public class ServiceSynchronize extends LifecycleService {
// Listen for connection changes // Listen for connection changes
istore.addConnectionListener(new ConnectionAdapter() { istore.addConnectionListener(new ConnectionAdapter() {
List<Thread> folderThreads = new ArrayList<>();
Map<Long, IMAPFolder> mapFolder = new HashMap<>(); Map<Long, IMAPFolder> mapFolder = new HashMap<>();
@Override @Override
public void opened(ConnectionEvent e) { public void opened(ConnectionEvent e) {
Log.i(Helper.TAG, account.name + " opened"); Log.i(Helper.TAG, account.name + " opened");
try {
DB db = DB.getInstance(ServiceSynchronize.this);
DB db = DB.getInstance(ServiceSynchronize.this);
account.error = null;
db.account().updateAccount(account);
try {
synchronizeFolders(account, fstore); synchronizeFolders(account, fstore);
for (final EntityFolder folder : db.folder().getFolders(account.id, true)) { for (final EntityFolder folder : db.folder().getFolders(account.id, true)) {
Log.i(Helper.TAG, account.name + " sync folder " + folder.name); Log.i(Helper.TAG, account.name + " sync folder " + folder.name);
Thread thread = new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
IMAPFolder ifolder = null; IMAPFolder ifolder = null;
DB db = DB.getInstance(ServiceSynchronize.this);
try { try {
Log.i(Helper.TAG, folder.name + " start"); Log.i(Helper.TAG, folder.name + " start");
@ -371,19 +374,25 @@ public class ServiceSynchronize extends LifecycleService {
mapFolder.put(folder.id, ifolder); mapFolder.put(folder.id, ifolder);
} }
folder.error = null;
db.folder().updateFolder(folder);
monitorFolder(account, folder, fstore, ifolder, state); monitorFolder(account, folder, fstore, ifolder, state);
} catch (FolderNotFoundException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
folder.error = Helper.formatThrowable(ex);
db.folder().updateFolder(folder);
// Cascade up // Cascade up
try { if (!(ex instanceof FolderNotFoundException))
fstore.close(); try {
} catch (MessagingException e1) { fstore.close();
Log.w(Helper.TAG, account.name + " " + e1 + "\n" + Log.getStackTraceString(e1)); } catch (MessagingException e1) {
} Log.w(Helper.TAG, account.name + " " + e1 + "\n" + Log.getStackTraceString(e1));
}
} finally { } finally {
if (ifolder != null && ifolder.isOpen()) { if (ifolder != null && ifolder.isOpen()) {
try { try {
@ -395,15 +404,14 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, folder.name + " stop"); Log.i(Helper.TAG, folder.name + " stop");
} }
} }
}, "sync.folder." + folder.id); }, "sync.folder." + folder.id).start();
folderThreads.add(thread);
thread.start();
} }
IntentFilter f = new IntentFilter(ACTION_PROCESS_FOLDER); IntentFilter f = new IntentFilter(ACTION_PROCESS_FOLDER);
f.addDataType("account/" + account.id); f.addDataType("account/" + account.id);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this);
lbm.registerReceiver(processReceiver, f); lbm.registerReceiver(processReceiver, f);
Log.i(Helper.TAG, "listen process folder"); Log.i(Helper.TAG, "listen process folder");
for (final EntityFolder folder : db.folder().getFolders(account.id)) for (final EntityFolder folder : db.folder().getFolders(account.id))
if (!EntityFolder.OUTBOX.equals(folder.type)) if (!EntityFolder.OUTBOX.equals(folder.type))
@ -415,6 +423,9 @@ public class ServiceSynchronize extends LifecycleService {
Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, null, ex); reportError(account.name, null, ex);
account.error = Helper.formatThrowable(ex);
db.account().updateAccount(account);
// Cascade up // Cascade up
try { try {
fstore.close(); fstore.close();
@ -490,8 +501,6 @@ public class ServiceSynchronize extends LifecycleService {
} }
processOperations(folder, fstore, ifolder); processOperations(folder, fstore, ifolder);
} catch (FolderNotFoundException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
@ -538,7 +547,11 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, account.name + " not running anymore"); Log.i(Helper.TAG, account.name + " not running anymore");
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, null, ex);
account.error = Helper.formatThrowable(ex);
DB.getInstance(this).account().updateAccount(account);
} finally { } finally {
if (istore != null) { if (istore != null) {
try { try {
@ -633,6 +646,9 @@ public class ServiceSynchronize extends LifecycleService {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
folder.error = Helper.formatThrowable(ex);
DB.getInstance(ServiceSynchronize.this).folder().updateFolder(folder);
// Cascade up // Cascade up
try { try {
istore.close(); istore.close();
@ -665,6 +681,9 @@ public class ServiceSynchronize extends LifecycleService {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
folder.error = Helper.formatThrowable(ex);
DB.getInstance(ServiceSynchronize.this).folder().updateFolder(folder);
// Cascade up // Cascade up
try { try {
istore.close(); istore.close();
@ -699,9 +718,10 @@ public class ServiceSynchronize extends LifecycleService {
" msg=" + op.message + " msg=" + op.message +
" args=" + op.args); " args=" + op.args);
JSONArray jargs = new JSONArray(op.args);
EntityMessage message = db.message().getMessage(op.message);
try { try {
JSONArray jargs = new JSONArray(op.args);
EntityMessage message = db.message().getMessage(op.message);
if (EntityOperation.SEEN.equals(op.name)) if (EntityOperation.SEEN.equals(op.name))
doSeen(folder, ifolder, jargs, message); doSeen(folder, ifolder, jargs, message);
@ -727,32 +747,28 @@ public class ServiceSynchronize extends LifecycleService {
// Operation succeeded // Operation succeeded
db.operation().deleteOperation(op.id); db.operation().deleteOperation(op.id);
} catch (Throwable ex) {
op.error = Helper.formatThrowable(ex);
db.operation().updateOperation(op);
if (ex instanceof MessageRemovedException ||
ex instanceof FolderNotFoundException ||
ex instanceof SMTPSendFailedException) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
// There is no use in repeating
db.operation().deleteOperation(op.id);
continue;
} else if (ex instanceof MessagingException) {
// Socket timeout is a recoverable condition (send message)
if (ex.getCause() instanceof SocketTimeoutException) {
Log.w(Helper.TAG, "Recoverable " + ex);
// No need to inform user
return;
}
}
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
// There is no use in repeating
db.operation().deleteOperation(op.id);
} catch (FolderNotFoundException ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
// There is no use in repeating
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
db.operation().deleteOperation(op.id);
throw ex; throw ex;
} catch (MessagingException ex) {
// Socket timeout is a recoverable condition (send message)
if (ex.getCause() instanceof SocketTimeoutException) {
Log.w(Helper.TAG, "Recoverable " + ex);
// No need to inform user
return;
} else
throw ex;
} }
} finally { } finally {
Log.i(Helper.TAG, folder.name + " end op=" + op.id + "/" + op.name); Log.i(Helper.TAG, folder.name + " end op=" + op.id + "/" + op.name);
@ -894,10 +910,17 @@ public class ServiceSynchronize extends LifecycleService {
itransport.connect(ident.host, ident.port, ident.user, ident.password); itransport.connect(ident.host, ident.port, ident.user, ident.password);
// Send message // Send message
Address[] to = imessage.getAllRecipients(); try {
itransport.sendMessage(imessage, to); Address[] to = imessage.getAllRecipients();
Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user + itransport.sendMessage(imessage, to);
" to " + TextUtils.join(", ", to)); Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user +
" to " + TextUtils.join(", ", to));
} catch (SMTPSendFailedException ex) {
// TODO: response codes: https://www.ietf.org/rfc/rfc821.txt
message.error = Helper.formatThrowable(ex);
db.message().updateMessage(message);
throw ex;
}
try { try {
db.beginTransaction(); db.beginTransaction();

@ -65,6 +65,19 @@
app:layout_constraintStart_toEndOf="@id/tvHost" app:layout_constraintStart_toEndOf="@id/tvHost"
app:layout_constraintTop_toBottomOf="@id/ivSync" /> app:layout_constraintTop_toBottomOf="@id/ivSync" />
<TextView
android:id="@+id/tvError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginStart="6dp"
android:text="error"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvUser" />
<View <View
android:id="@+id/vSeparator" android:id="@+id/vSeparator"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -72,5 +85,5 @@
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:background="?attr/colorSeparator" android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvHost" /> app:layout_constraintTop_toBottomOf="@id/tvError" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -71,6 +71,19 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivMessages" /> app:layout_constraintTop_toBottomOf="@id/ivMessages" />
<TextView
android:id="@+id/tvError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginStart="6dp"
android:text="error"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivSync" />
<View <View
android:id="@+id/vSeparator" android:id="@+id/vSeparator"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -78,5 +91,5 @@
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:background="?attr/colorSeparator" android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivSync" /> app:layout_constraintTop_toBottomOf="@id/tvError" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -65,6 +65,19 @@
app:layout_constraintBottom_toBottomOf="@id/tvSubject" app:layout_constraintBottom_toBottomOf="@id/tvSubject"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tvError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginStart="6dp"
android:text="error"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSubject" />
<ProgressBar <ProgressBar
android:id="@+id/pbLoading" android:id="@+id/pbLoading"
style="@style/Base.Widget.AppCompat.ProgressBar" style="@style/Base.Widget.AppCompat.ProgressBar"
@ -84,5 +97,5 @@
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:background="?attr/colorSeparator" android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSubject" /> app:layout_constraintTop_toBottomOf="@id/tvError" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save