Added setting to poll instead of synchronize

This commit is contained in:
M66B
2018-12-05 10:25:35 +01:00
parent 17bfeceb03
commit 3aa89cae97
9 changed files with 1405 additions and 170 deletions

View File

@@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 15,
version = 16,
entities = {
EntityIdentity.class,
EntityAccount.class,
@@ -241,6 +241,13 @@ public abstract class DB extends RoomDatabase {
db.execSQL("ALTER TABLE `folder` ADD COLUMN `sync_state` TEXT");
}
})
.addMigrations(new Migration(15, 16) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `folder` ADD COLUMN `poll` INTEGER NOT NULL DEFAULT 0");
}
})
.build();
}

View File

@@ -149,6 +149,7 @@ public interface DaoFolder {
", display = :display" +
", hide = :hide" +
", synchronize = :synchronize" +
", poll = :poll" +
", unified = :unified" +
", `sync_days` = :sync_days" +
", `keep_days` = :keep_days" +
@@ -158,6 +159,7 @@ public interface DaoFolder {
String name, String display,
boolean hide,
boolean synchronize,
boolean poll,
boolean unified,
int sync_days, int keep_days);

View File

@@ -69,6 +69,8 @@ public class EntityFolder implements Serializable {
@NonNull
public Boolean synchronize;
@NonNull
public Boolean poll = false;
@NonNull
public Integer sync_days;
@NonNull
public Integer keep_days;
@@ -168,6 +170,9 @@ public class EntityFolder implements Serializable {
this.type.equals(other.type) &&
this.level.equals(other.level) &&
this.synchronize.equals(other.synchronize) &&
this.poll.equals(other.poll) &&
this.sync_days.equals(other.sync_days) &&
this.keep_days.equals(other.keep_days) &&
(this.display == null ? other.display == null : this.display.equals(other.display)) &&
this.hide == other.hide &&
this.unified == other.unified &&
@@ -189,6 +194,7 @@ public class EntityFolder implements Serializable {
json.put("type", type);
json.put("level", level);
json.put("synchronize", synchronize);
json.put("poll", poll);
json.put("sync_days", sync_days);
json.put("keep_days", keep_days);
json.put("display", display);
@@ -208,6 +214,10 @@ public class EntityFolder implements Serializable {
folder.level = 0;
folder.synchronize = json.getBoolean("synchronize");
if (json.has("poll"))
folder.poll = json.getBoolean("poll");
else
folder.poll = false;
if (json.has("after"))
folder.sync_days = json.getInt("after");

View File

@@ -29,6 +29,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
@@ -52,6 +53,7 @@ public class FragmentFolder extends FragmentEx {
private EditText etDisplay;
private CheckBox cbHide;
private CheckBox cbSynchronize;
private CheckBox cbPoll;
private CheckBox cbUnified;
private EditText etSyncDays;
private EditText etKeepDays;
@@ -85,6 +87,7 @@ public class FragmentFolder extends FragmentEx {
etDisplay = view.findViewById(R.id.etDisplay);
cbHide = view.findViewById(R.id.cbHide);
cbSynchronize = view.findViewById(R.id.cbSynchronize);
cbPoll = view.findViewById(R.id.cbPoll);
cbUnified = view.findViewById(R.id.cbUnified);
etSyncDays = view.findViewById(R.id.etSyncDays);
etKeepDays = view.findViewById(R.id.etKeepDays);
@@ -93,6 +96,13 @@ public class FragmentFolder extends FragmentEx {
pbSave = view.findViewById(R.id.pbSave);
pbWait = view.findViewById(R.id.pbWait);
cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
cbPoll.setEnabled(isChecked);
}
});
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -109,6 +119,7 @@ public class FragmentFolder extends FragmentEx {
args.putBoolean("hide", cbHide.isChecked());
args.putBoolean("unified", cbUnified.isChecked());
args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putBoolean("poll", cbPoll.isChecked());
args.putString("sync", etSyncDays.getText().toString());
args.putString("keep", etKeepDays.getText().toString());
@@ -122,6 +133,7 @@ public class FragmentFolder extends FragmentEx {
boolean hide = args.getBoolean("hide");
boolean unified = args.getBoolean("unified");
boolean synchronize = args.getBoolean("synchronize");
boolean poll = args.getBoolean("poll");
String sync = args.getString("sync");
String keep = args.getString("keep");
@@ -132,7 +144,8 @@ public class FragmentFolder extends FragmentEx {
if (keep_days < sync_days)
keep_days = sync_days;
EntityFolder folder = null;
boolean reload = false;
EntityFolder folder;
IMAPStore istore = null;
DB db = DB.getInstance(getContext());
@@ -167,6 +180,7 @@ public class FragmentFolder extends FragmentEx {
create.type = EntityFolder.USER;
create.unified = unified;
create.synchronize = synchronize;
create.poll = poll;
create.sync_days = sync_days;
create.keep_days = keep_days;
db.folder().insertFolder(create);
@@ -182,6 +196,10 @@ public class FragmentFolder extends FragmentEx {
}
if (folder != null) {
reload = (!folder.name.equals(name) ||
!folder.synchronize.equals(synchronize) ||
!folder.poll.equals(poll));
Calendar cal_keep = Calendar.getInstance();
cal_keep.add(Calendar.DAY_OF_MONTH, -keep_days);
cal_keep.set(Calendar.HOUR_OF_DAY, 0);
@@ -194,7 +212,12 @@ public class FragmentFolder extends FragmentEx {
keep_time = 0;
Log.i(Helper.TAG, "Updating folder=" + name);
db.folder().setFolderProperties(id, name, display, hide, synchronize, unified, sync_days, keep_days);
db.folder().setFolderProperties(id,
name, display,
hide,
synchronize, poll,
unified,
sync_days, keep_days);
db.message().deleteMessagesBefore(id, keep_time, true);
@@ -210,7 +233,7 @@ public class FragmentFolder extends FragmentEx {
istore.close();
}
if (folder == null || !folder.name.equals(name))
if (folder == null || !folder.name.equals(name) || reload)
ServiceSynchronize.reload(getContext(), "save folder");
else
EntityOperation.sync(db, folder.id);
@@ -350,6 +373,7 @@ public class FragmentFolder extends FragmentEx {
cbHide.setChecked(folder == null ? false : folder.hide);
cbUnified.setChecked(folder == null ? false : folder.unified);
cbSynchronize.setChecked(folder == null || folder.synchronize);
cbPoll.setChecked(folder == null ? false : folder.poll);
etSyncDays.setText(Integer.toString(folder == null ? EntityFolder.DEFAULT_USER_SYNC : folder.sync_days));
etKeepDays.setText(Integer.toString(folder == null ? EntityFolder.DEFAULT_USER_SYNC : folder.keep_days));
}
@@ -358,6 +382,7 @@ public class FragmentFolder extends FragmentEx {
pbWait.setVisibility(View.GONE);
Helper.setViewsEnabled(view, true);
etRename.setEnabled(folder == null || EntityFolder.USER.equals(folder.type));
cbPoll.setEnabled(cbSynchronize.isChecked());
btnSave.setEnabled(true);
ibDelete.setVisibility(folder == null || !EntityFolder.USER.equals(folder.type) ? View.GONE : View.VISIBLE);
}

View File

@@ -886,53 +886,134 @@ public class ServiceSynchronize extends LifecycleService {
// Update folder list
synchronizeFolders(account, istore, state);
// Open folders
for (final EntityFolder folder : db.folder().getFolders(account.id, true)) {
Log.i(Helper.TAG, account.name + " sync folder " + folder.name);
// Open synchronizing folders
for (final EntityFolder folder : db.folder().getFolders(account.id)) {
if (folder.synchronize && !folder.poll && capIdle) {
Log.i(Helper.TAG, account.name + " sync folder " + folder.name);
db.folder().setFolderState(folder.id, "connecting");
db.folder().setFolderState(folder.id, "connecting");
final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
try {
ifolder.open(Folder.READ_WRITE);
} catch (Throwable ex) {
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
throw ex;
}
folders.put(folder, ifolder);
final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
try {
ifolder.open(Folder.READ_WRITE);
} catch (Throwable ex) {
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
throw ex;
}
folders.put(folder, ifolder);
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
Log.i(Helper.TAG, account.name + " folder " + folder.name + " flags=" + ifolder.getPermanentFlags());
Log.i(Helper.TAG, account.name + " folder " + folder.name + " flags=" + ifolder.getPermanentFlags());
// Listen for new and deleted messages
ifolder.addMessageCountListener(new MessageCountAdapter() {
@Override
public void messagesAdded(MessageCountEvent e) {
try {
wlAccount.acquire();
Log.i(Helper.TAG, folder.name + " messages added");
// Listen for new and deleted messages
ifolder.addMessageCountListener(new MessageCountAdapter() {
@Override
public void messagesAdded(MessageCountEvent e) {
try {
wlAccount.acquire();
Log.i(Helper.TAG, folder.name + " messages added");
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
fp.add(FetchProfile.Item.SIZE);
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
ifolder.fetch(e.getMessages(), fp);
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
fp.add(FetchProfile.Item.SIZE);
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
ifolder.fetch(e.getMessages(), fp);
for (Message imessage : e.getMessages())
for (Message imessage : e.getMessages())
try {
long id;
try {
db.beginTransaction();
id = synchronizeMessage(
ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) imessage,
false, false, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
try {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, id);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
} catch (IOException ex) {
if (ex.getCause() instanceof MessageRemovedException)
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
else
throw ex;
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
@Override
public void messagesRemoved(MessageCountEvent e) {
try {
wlAccount.acquire();
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);
int count = db.message().deleteMessage(folder.id, uid);
Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count);
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
});
// Flags (like "seen") at the remote could be changed while synchronizing
// Listen for changed messages
ifolder.addMessageChangedListener(new MessageChangedListener() {
@Override
public void messageChanged(MessageChangedEvent e) {
try {
wlAccount.acquire();
try {
Log.i(Helper.TAG, folder.name + " message changed");
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.FLAGS);
ifolder.fetch(new Message[]{e.getMessage()}, fp);
long id;
try {
db.beginTransaction();
id = synchronizeMessage(
ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) imessage,
folder, ifolder, (IMAPMessage) e.getMessage(),
false, false, false);
db.setTransactionSuccessful();
} finally {
@@ -941,7 +1022,7 @@ public class ServiceSynchronize extends LifecycleService {
try {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, id);
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), id);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@@ -954,99 +1035,18 @@ public class ServiceSynchronize extends LifecycleService {
else
throw ex;
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
@Override
public void messagesRemoved(MessageCountEvent e) {
try {
wlAccount.acquire();
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);
int count = db.message().deleteMessage(folder.id, uid);
Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count);
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
});
// Flags (like "seen") at the remote could be changed while synchronizing
// Listen for changed messages
ifolder.addMessageChangedListener(new MessageChangedListener() {
@Override
public void messageChanged(MessageChangedEvent e) {
try {
wlAccount.acquire();
try {
Log.i(Helper.TAG, folder.name + " message changed");
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.FLAGS);
ifolder.fetch(new Message[]{e.getMessage()}, fp);
long id;
try {
db.beginTransaction();
id = synchronizeMessage(
ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) e.getMessage(),
false, false, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
try {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), id);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
} catch (IOException ex) {
if (ex.getCause() instanceof MessageRemovedException)
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
else
throw ex;
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
});
});
// Idle folder
if (capIdle) {
// Idle folder
Thread idler = new Thread(new Runnable() {
@Override
public void run() {
@@ -1068,13 +1068,12 @@ public class ServiceSynchronize extends LifecycleService {
}, "idler." + folder.id);
idler.start();
idlers.add(idler);
}
EntityOperation.sync(db, folder.id);
}
EntityOperation.sync(db, folder.id);
} else
folders.put(folder, null);
// Observe folder operations
for (final EntityFolder folder : db.folder().getFolders(account.id)) {
// Observe operations
Handler handler = new Handler(getMainLooper()) {
private List<Long> handling = new ArrayList<>();
private final PowerManager.WakeLock wlFolder = pm.newWakeLock(
@@ -1109,55 +1108,50 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, folder.name + " process");
// Get folder
EntityFolder ofolder = null;
IMAPFolder ifolder = null;
for (EntityFolder f : folders.keySet())
if (f.id.equals(folder.id)) {
ofolder = f;
ifolder = folders.get(f);
ifolder = folders.get(f); // null when polling
break;
}
final boolean shouldClose = (ofolder == null);
final boolean shouldClose = (ifolder == null);
try {
if (ofolder == null)
ofolder = db.folder().getFolder(folder.id);
Log.i(Helper.TAG, ofolder.name + " run " + (shouldClose ? "offline" : "online"));
Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online"));
if (ifolder == null) {
// Prevent unnecessary folder connections
if (db.operation().getOperationCount(ofolder.id, null) == 0)
if (db.operation().getOperationCount(folder.id, null) == 0)
return;
db.folder().setFolderState(ofolder.id, "connecting");
db.folder().setFolderState(folder.id, "connecting");
ifolder = (IMAPFolder) istore.getFolder(ofolder.name);
ifolder = (IMAPFolder) istore.getFolder(folder.name);
ifolder.open(Folder.READ_WRITE);
db.folder().setFolderState(ofolder.id, "connected");
db.folder().setFolderError(ofolder.id, null);
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
}
processOperations(account, ofolder, isession, istore, ifolder, state);
processOperations(account, folder, isession, istore, ifolder, state);
} catch (Throwable ex) {
Log.e(Helper.TAG, ofolder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, ofolder, ex);
db.folder().setFolderError(ofolder.id, Helper.formatThrowable(ex));
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
if (shouldClose) {
if (ifolder != null && ifolder.isOpen()) {
db.folder().setFolderState(ofolder.id, "closing");
db.folder().setFolderState(folder.id, "closing");
try {
ifolder.close(false);
} catch (MessagingException ex) {
Log.w(Helper.TAG, ofolder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
}
db.folder().setFolderState(ofolder.id, null);
db.folder().setFolderState(folder.id, null);
}
}
} finally {
@@ -1170,6 +1164,8 @@ public class ServiceSynchronize extends LifecycleService {
});
}
};
// Start watching for operations
handler.sendEmptyMessage(1);
handlers.add(handler);
}
@@ -1197,11 +1193,12 @@ public class ServiceSynchronize extends LifecycleService {
throw new StoreClosedException(istore);
for (EntityFolder folder : folders.keySet())
if (capIdle) {
if (!folders.get(folder).isOpen())
throw new FolderClosedException(folders.get(folder));
} else
synchronizeMessages(account, folder, folders.get(folder), state);
if (folder.synchronize)
if (!folder.poll && capIdle) {
if (!folders.get(folder).isOpen())
throw new FolderClosedException(folders.get(folder));
} else
EntityOperation.sync(db, folder.id);
// Successfully connected: reset back off time
backoff = CONNECT_BACKOFF_START;
@@ -1230,10 +1227,6 @@ public class ServiceSynchronize extends LifecycleService {
// Cleanup
am.cancel(pi);
unregisterReceiver(alarm);
for (Handler handler : handlers)
handler.sendEmptyMessage(0);
handlers.clear();
}
Log.i(Helper.TAG, account.name + " done state=" + state);
@@ -1244,10 +1237,16 @@ public class ServiceSynchronize extends LifecycleService {
EntityLog.log(ServiceSynchronize.this, account.name + " " + Helper.formatThrowable(ex));
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
} finally {
// Stop watching for operations
for (Handler handler : handlers)
handler.sendEmptyMessage(0);
handlers.clear();
EntityLog.log(this, account.name + " closing");
db.account().setAccountState(account.id, "closing");
for (EntityFolder folder : folders.keySet())
db.folder().setFolderState(folder.id, "closing");
if (folder.synchronize && !folder.poll)
db.folder().setFolderState(folder.id, "closing");
// Close store
try {
@@ -1267,7 +1266,8 @@ public class ServiceSynchronize extends LifecycleService {
idlers.clear();
for (EntityFolder folder : folders.keySet())
db.folder().setFolderState(folder.id, null);
if (folder.synchronize && !folder.poll)
db.folder().setFolderState(folder.id, null);
}
if (state.running())