diff --git a/FAQ.md b/FAQ.md index 5435019d0c..4c80487db4 100644 --- a/FAQ.md +++ b/FAQ.md @@ -28,7 +28,7 @@ For authorizing: ## Planned features -* Synchronize on demand +* ~~Synchronize on demand~~ Anything on this list is in random order and *might* be added in the near future. diff --git a/app/src/main/java/eu/faircode/email/AdapterFolder.java b/app/src/main/java/eu/faircode/email/AdapterFolder.java index 06a224191d..a1a7f027af 100644 --- a/app/src/main/java/eu/faircode/email/AdapterFolder.java +++ b/app/src/main/java/eu/faircode/email/AdapterFolder.java @@ -38,8 +38,6 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.google.android.material.snackbar.Snackbar; - import java.util.ArrayList; import java.util.List; @@ -236,7 +234,8 @@ public class AdapterFolder extends RecyclerView.Adapter() { + new SimpleTask() { @Override - protected Boolean onExecute(Context context, Bundle args) { + protected Void onExecute(Context context, Bundle args) { long aid = args.getLong("account"); long fid = args.getLong("folder"); DB db = DB.getInstance(context); - EntityOperation.sync(db, fid); + try { + db.beginTransaction(); + + if (aid < 0) // outbox + EntityOperation.sync(db, fid); + else { + if ("connected".equals(db.account().getAccount(aid).state)) + EntityOperation.sync(db, fid); + else { + db.folder().setFolderSyncState(folder.id, "requested"); + ServiceSynchronize.sync(context, fid); + } + } - if (aid < 0) // outbox - return "connected".equals(db.folder().getFolder(fid).state); - else - return "connected".equals(db.account().getAccount(aid).state); - } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - @Override - protected void onExecuted(Bundle args, Boolean connected) { - if (!connected) - Snackbar.make(itemView, R.string.title_sync_queued, Snackbar.LENGTH_LONG).show(); + return null; } @Override diff --git a/app/src/main/java/eu/faircode/email/EntityFolder.java b/app/src/main/java/eu/faircode/email/EntityFolder.java index aa7623172b..42640d51cf 100644 --- a/app/src/main/java/eu/faircode/email/EntityFolder.java +++ b/app/src/main/java/eu/faircode/email/EntityFolder.java @@ -29,6 +29,7 @@ import java.text.Collator; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.Date; import java.util.List; import java.util.Locale; @@ -162,6 +163,17 @@ public class EntityFolder implements Serializable { public EntityFolder() { } + long getSyncDays() { + int days = sync_days; + if (last_sync != null) { + int ago_days = (int) ((new Date().getTime() - last_sync) / (24 * 3600 * 1000L)) + 1; + if (ago_days > days) + days = ago_days; + } + + return (initialize ? Math.min(DEFAULT_INIT, keep_days) : days); + } + static int getIcon(String type) { if (EntityFolder.INBOX.equals(type)) return R.drawable.baseline_move_to_inbox_24; diff --git a/app/src/main/java/eu/faircode/email/EntityOperation.java b/app/src/main/java/eu/faircode/email/EntityOperation.java index f4f07f9dfd..c0336f221e 100644 --- a/app/src/main/java/eu/faircode/email/EntityOperation.java +++ b/app/src/main/java/eu/faircode/email/EntityOperation.java @@ -102,15 +102,8 @@ public class EntityOperation { EntityFolder folder = db.folder().getFolder(fid); - int sync_days = folder.sync_days; - if (folder.last_sync != null) { - int ago_days = (int) ((new Date().getTime() - folder.last_sync) / (24 * 3600 * 1000L)) + 1; - if (ago_days > sync_days) - sync_days = ago_days; - } - JSONArray jargs = new JSONArray(); - jargs.put(folder.initialize ? Math.min(EntityFolder.DEFAULT_INIT, folder.keep_days) : sync_days); + jargs.put(folder.getSyncDays()); jargs.put(folder.keep_days); jargs.put(folder.download); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 2ca341748a..1861bc49b6 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -487,22 +487,10 @@ public class FragmentMessages extends FragmentBase { args.putLong("folder", folder); new SimpleTask() { - @Override - protected void onPreExecute(Bundle args) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - boolean enabled = prefs.getBoolean("enabled", true); - if (!enabled) { - prefs.edit().putBoolean("enabled", true).apply(); - ServiceSynchronize.reload(getContext(), "refresh"); - } - } - @Override protected Boolean onExecute(Context context, Bundle args) { long fid = args.getLong("folder"); - boolean connected = false; - DB db = DB.getInstance(context); try { db.beginTransaction(); @@ -518,33 +506,34 @@ public class FragmentMessages extends FragmentBase { folders.add(folder); } - for (EntityFolder folder : folders) { - EntityOperation.sync(db, folder.id); - + boolean now = false; + for (EntityFolder folder : folders) if (folder.account == null) { // outbox - if ("connected".equals(folder.state)) - connected = true; + now = ("connected".equals(folder.state)); + EntityOperation.sync(db, folder.id); } else { + now = true; EntityAccount account = db.account().getAccount(folder.account); if ("connected".equals(account.state)) - connected = true; + EntityOperation.sync(db, folder.id); + else { + db.folder().setFolderSyncState(folder.id, "requested"); + ServiceSynchronize.sync(context, folder.id); + } } - } db.setTransactionSuccessful(); + + return now; } finally { db.endTransaction(); } - - return connected; } @Override - protected void onExecuted(Bundle args, Boolean connected) { - if (!connected) { + protected void onExecuted(Bundle args, Boolean now) { + if (!now) swipeRefresh.setRefreshing(false); - Snackbar.make(view, R.string.title_sync_queued, Snackbar.LENGTH_LONG).show(); - } } @Override diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 0c35cb7c6b..55836f0cc7 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -343,6 +343,15 @@ public class ServiceSynchronize extends LifecycleService { serviceManager.service_reload(intent.getStringExtra("reason")); break; + case "synchronize": + executor.submit(new Runnable() { + @Override + public void run() { + synchronizeOnDemand(Long.parseLong(parts[1])); + } + }); + break; + case "summary": case "clear": case "seen": @@ -2210,6 +2219,77 @@ public class ServiceSynchronize extends LifecycleService { } } + private void synchronizeOnDemand(long fid) { + Log.i("Synchronize on demand folder=" + fid); + + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + PowerManager.WakeLock wlAccount = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":sync." + fid); + + DB db = DB.getInstance(this); + EntityFolder folder = null; + EntityAccount account = null; + + Store istore = null; + try { + wlAccount.acquire(); + + folder = db.folder().getFolder(fid); + account = db.account().getAccount(folder.account); + + // Create session + Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure); + final Session isession = Session.getInstance(props, null); + isession.setDebug(true); + + // Connect account + Log.i(account.name + " connecting"); + db.account().setAccountState(account.id, "connecting"); + istore = isession.getStore(account.getProtocol()); + Helper.connect(this, istore, account); + db.account().setAccountState(account.id, "connected"); + Log.i(account.name + " connected"); + + // Connect folder + Log.i(folder.name + " connecting"); + db.folder().setFolderState(folder.id, "connecting"); + Folder ifolder = istore.getFolder(folder.name); + ifolder.open(Folder.READ_WRITE); + db.folder().setFolderState(folder.id, "connected"); + db.folder().setFolderError(folder.id, null); + Log.i(folder.name + " connected"); + + // Synchronize messages + JSONArray jargs = new JSONArray(); + jargs.put(folder.getSyncDays()); + jargs.put(folder.keep_days); + jargs.put(folder.download); + synchronizeMessages(account, folder, (IMAPFolder) ifolder, jargs, new ServiceState()); + + } catch (Throwable ex) { + db.account().setAccountError(account.id, Helper.formatThrowable(ex)); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + } finally { + if (istore != null) { + Log.i(account.name + " closing"); + db.account().setAccountState(account.id, "closing"); + db.folder().setFolderState(folder.id, "closing"); + + try { + istore.close(); + } catch (MessagingException ex) { + Log.e(ex); + } + + db.account().setAccountState(account.id, null); + db.folder().setFolderState(folder.id, null); + Log.i(account.name + " closed"); + } + + wlAccount.release(); + } + } + private void synchronizeFolders(EntityAccount account, Store istore, ServiceState state) throws MessagingException { DB db = DB.getInstance(this); try { @@ -3273,7 +3353,7 @@ public class ServiceSynchronize extends LifecycleService { try { wl.acquire(); - EntityLog.log(ServiceSynchronize.this, "Reload " + + EntityLog.log(ServiceSynchronize.this, "Reload" + " stop=" + doStop + " start=" + doStart + " queued=" + queued + " " + reason); if (doStop) @@ -3339,6 +3419,12 @@ public class ServiceSynchronize extends LifecycleService { .putExtra("reason", reason)); } + public static void sync(Context context, long folder) { + ContextCompat.startForegroundService(context, + new Intent(context, ServiceSynchronize.class) + .setAction("synchronize:" + folder)); + } + private class ServiceState { private Thread thread; private Semaphore semaphore = new Semaphore(0); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 562f50d111..d125074868 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -353,7 +353,6 @@ Showing the original message can leak privacy sensitive information Showing images can leak privacy sensitive information Edit reformatted replied/forwarded message text? - Synchronization will take place on next account connection Fix Compose