mirror of
https://github.com/M66B/FairEmail.git
synced 2026-01-02 19:10:11 +01:00
Revised operation handling
This commit is contained in:
@@ -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 * FROM operation WHERE folder = :folder ORDER BY id")
|
||||
List<EntityOperation> getOperations(long folder);
|
||||
|
||||
@Query("SELECT COUNT(operation.id) FROM operation" +
|
||||
" JOIN message ON message.id = operation.message" +
|
||||
" WHERE folder = :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 ("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.uid = null;
|
||||
draft.ui_hide = false;
|
||||
draft.id = db.message().insertMessage(draft);
|
||||
EntityOperation.queue(db, draft, EntityOperation.ADD);
|
||||
|
||||
} else if ("trash".equals(action)) {
|
||||
EntityFolder trash = db.folder().getFolderByType(ident.account, EntityFolder.TRASH);
|
||||
|
||||
boolean move = (draft.uid != null);
|
||||
if (move)
|
||||
EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id, draft.uid);
|
||||
|
||||
draft.folder = trash.id;
|
||||
draft.uid = null;
|
||||
db.message().updateMessage(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.DELETE);
|
||||
// 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(getContext(), draft, EntityOperation.SEND);
|
||||
|
||||
} else if ("save".equals(action))
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.ADD);
|
||||
|
||||
else if ("trash".equals(action)) {
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.DELETE);
|
||||
|
||||
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);
|
||||
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.ADD);
|
||||
}
|
||||
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() {
|
||||
@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);
|
||||
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 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() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
message.ui_hide = true;
|
||||
db.message().updateMessage(message);
|
||||
final MenuItem item = bottom_navigation.getMenu().findItem(R.id.action_trash);
|
||||
item.setEnabled(false);
|
||||
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.DELETE);
|
||||
EntityOperation.process(getContext());
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
final Drawable icon = item.getIcon();
|
||||
item.setIcon(Helper.toDimmed(icon));
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
|
||||
new SimpleLoader() {
|
||||
@Override
|
||||
public Object onLoad(Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
DB db = DB.getInstance(getContext());
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
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));
|
||||
if (EntityOperation.SEEN.equals(op.name))
|
||||
doSeen(ifolder, jargs, message);
|
||||
|
||||
} else if (EntityOperation.ADD.equals(op.name)) {
|
||||
// Append message
|
||||
EntityMessage msg = message.getMessage(op.message);
|
||||
if (msg == null)
|
||||
return;
|
||||
else if (EntityOperation.ADD.equals(op.name))
|
||||
doAdd(ifolder, message);
|
||||
|
||||
// Disconnect from remote to prevent deletion
|
||||
Long uid = msg.uid;
|
||||
if (msg.uid != null) {
|
||||
msg.uid = null;
|
||||
message.updateMessage(msg);
|
||||
}
|
||||
else if (EntityOperation.MOVE.equals(op.name))
|
||||
doMove(folder, istore, ifolder, db, jargs, message);
|
||||
|
||||
// Execute append
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Session isession = Session.getInstance(props, null);
|
||||
MimeMessage imessage = MessageHelper.from(msg, isession);
|
||||
ifolder.appendMessages(new Message[]{imessage});
|
||||
else if (EntityOperation.DELETE.equals(op.name))
|
||||
doDelete(folder, ifolder, db, message);
|
||||
|
||||
// Drafts can be appended multiple times
|
||||
if (uid != null) {
|
||||
Message previously = ifolder.getMessageByUID(uid);
|
||||
if (previously == null)
|
||||
throw new MessageRemovedException();
|
||||
else if (EntityOperation.SEND.equals(op.name))
|
||||
doSend(db, message);
|
||||
|
||||
previously.setFlag(Flags.Flag.DELETED, true);
|
||||
ifolder.expunge();
|
||||
}
|
||||
else if (EntityOperation.ATTACHMENT.equals(op.name))
|
||||
doAttachment(ifolder, db, op, jargs, message);
|
||||
|
||||
} 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);
|
||||
|
||||
} else if (EntityOperation.SEND.equals(op.name)) {
|
||||
// Send message
|
||||
EntityMessage msg = message.getMessage(op.message);
|
||||
if (msg == null)
|
||||
return;
|
||||
|
||||
EntityMessage reply = (msg.replying == null ? null : message.getMessage(msg.replying));
|
||||
EntityIdentity ident = db.identity().getIdentity(msg.identity);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
message.updateMessage(msg);
|
||||
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
92
app/src/main/java/eu/faircode/email/SimpleLoader.java
Normal file
92
app/src/main/java/eu/faircode/email/SimpleLoader.java
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user