Simple email application for Android. Original source code: https://framagit.org/dystopia-project/simple-email

266 lines
11 KiB

6 years ago
  1. package eu.faircode.email;
  2. /*
  3. This file is part of FairEmail.
  4. FairEmail is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. NetGuard is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
  14. Copyright 2018 by Marcel Bokhorst (M66B)
  15. */
  16. import android.content.Context;
  17. import android.os.Handler;
  18. import android.util.Log;
  19. import com.sun.mail.imap.IMAPFolder;
  20. import com.sun.mail.imap.IMAPMessage;
  21. import com.sun.mail.imap.IMAPStore;
  22. import com.sun.mail.util.FolderClosedIOException;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.List;
  26. import java.util.Properties;
  27. import java.util.concurrent.ExecutorService;
  28. import java.util.concurrent.Executors;
  29. import javax.mail.FetchProfile;
  30. import javax.mail.Folder;
  31. import javax.mail.FolderClosedException;
  32. import javax.mail.Message;
  33. import javax.mail.MessageRemovedException;
  34. import javax.mail.Session;
  35. import javax.mail.UIDFolder;
  36. import javax.mail.search.BodyTerm;
  37. import javax.mail.search.FromStringTerm;
  38. import javax.mail.search.OrTerm;
  39. import javax.mail.search.RecipientStringTerm;
  40. import javax.mail.search.SubjectTerm;
  41. import androidx.lifecycle.GenericLifecycleObserver;
  42. import androidx.lifecycle.Lifecycle;
  43. import androidx.lifecycle.LifecycleOwner;
  44. import androidx.paging.PagedList;
  45. public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMessageEx> {
  46. private Context context;
  47. private long fid;
  48. private String search;
  49. private int pageSize;
  50. private Handler handler;
  51. private IBoundaryCallbackMessages intf;
  52. private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
  53. private IMAPStore istore = null;
  54. private IMAPFolder ifolder = null;
  55. private Message[] imessages = null;
  56. private List<Long> existing = new ArrayList<>();
  57. private int index;
  58. private boolean searching = false;
  59. private int loaded = 0;
  60. interface IBoundaryCallbackMessages {
  61. void onLoading();
  62. void onLoaded();
  63. void onError(Context context, Throwable ex);
  64. }
  65. BoundaryCallbackMessages(Context _context, LifecycleOwner owner, long folder, String search, int pageSize, IBoundaryCallbackMessages intf) {
  66. this.context = _context;
  67. this.fid = folder;
  68. this.search = search;
  69. this.pageSize = pageSize;
  70. this.handler = new Handler();
  71. this.intf = intf;
  72. owner.getLifecycle().addObserver(new GenericLifecycleObserver() {
  73. @Override
  74. public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
  75. if (event == Lifecycle.Event.ON_DESTROY) {
  76. executor.submit(new Runnable() {
  77. @Override
  78. public void run() {
  79. Log.i(Helper.TAG, "Boundary close");
  80. DB db = DB.getInstance(context);
  81. for (long id : existing)
  82. db.message().setMessageFound(id, false);
  83. db.message().deleteFoundMessages();
  84. try {
  85. if (istore != null)
  86. istore.close();
  87. } catch (Throwable ex) {
  88. Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
  89. } finally {
  90. context = null;
  91. istore = null;
  92. ifolder = null;
  93. imessages = null;
  94. existing.clear();
  95. }
  96. }
  97. });
  98. }
  99. }
  100. });
  101. }
  102. boolean isSearching() {
  103. return searching;
  104. }
  105. int getLoaded() {
  106. return loaded;
  107. }
  108. @Override
  109. public void onZeroItemsLoaded() {
  110. Log.i(Helper.TAG, "onZeroItemsLoaded");
  111. load();
  112. }
  113. @Override
  114. public void onItemAtEndLoaded(final TupleMessageEx itemAtEnd) {
  115. Log.i(Helper.TAG, "onItemAtEndLoaded");
  116. load();
  117. }
  118. private void load() {
  119. executor.submit(new Runnable() {
  120. @Override
  121. public void run() {
  122. try {
  123. searching = true;
  124. handler.post(new Runnable() {
  125. @Override
  126. public void run() {
  127. intf.onLoading();
  128. }
  129. });
  130. DB db = DB.getInstance(context);
  131. EntityFolder folder = db.folder().getFolder(fid);
  132. if (folder.account == null) // outbox
  133. return;
  134. EntityAccount account = db.account().getAccount(folder.account);
  135. if (imessages == null) {
  136. Properties props = MessageHelper.getSessionProperties(account.auth_type);
  137. props.setProperty("mail.imap.throwsearchexception", "true");
  138. Session isession = Session.getInstance(props, null);
  139. Log.i(Helper.TAG, "Boundary connecting account=" + account.name);
  140. istore = (IMAPStore) isession.getStore("imaps");
  141. Helper.connect(context, istore, account);
  142. Log.i(Helper.TAG, "Boundary opening folder=" + folder.name);
  143. ifolder = (IMAPFolder) istore.getFolder(folder.name);
  144. ifolder.open(Folder.READ_WRITE);
  145. Log.i(Helper.TAG, "Boundary searching=" + search);
  146. if (search == null)
  147. imessages = ifolder.getMessages();
  148. else
  149. imessages = ifolder.search(
  150. new OrTerm(
  151. new OrTerm(
  152. new FromStringTerm(search),
  153. new RecipientStringTerm(Message.RecipientType.TO, search)
  154. ),
  155. new OrTerm(
  156. new SubjectTerm(search),
  157. new BodyTerm(search)
  158. )
  159. )
  160. );
  161. Log.i(Helper.TAG, "Boundary found messages=" + imessages.length);
  162. index = imessages.length - 1;
  163. }
  164. int count = 0;
  165. while (index >= 0 && count < pageSize) {
  166. Log.i(Helper.TAG, "Boundary index=" + index);
  167. int from = Math.max(0, index - (pageSize - count) + 1);
  168. Message[] isub = Arrays.copyOfRange(imessages, from, index + 1);
  169. index -= (pageSize - count);
  170. FetchProfile fp = new FetchProfile();
  171. fp.add(FetchProfile.Item.ENVELOPE);
  172. fp.add(FetchProfile.Item.FLAGS);
  173. fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
  174. fp.add(UIDFolder.FetchProfileItem.UID);
  175. fp.add(IMAPFolder.FetchProfileItem.HEADERS);
  176. fp.add(FetchProfile.Item.SIZE);
  177. fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
  178. ifolder.fetch(isub, fp);
  179. try {
  180. db.beginTransaction();
  181. for (int j = isub.length - 1; j >= 0; j--)
  182. try {
  183. long uid = ifolder.getUID(isub[j]);
  184. Log.i(Helper.TAG, "Boundary sync uid=" + uid);
  185. EntityMessage message = db.message().getMessageByUid(fid, uid);
  186. if (message == null) {
  187. ServiceSynchronize.synchronizeMessage(context, folder, ifolder, (IMAPMessage) isub[j], search != null);
  188. count++;
  189. loaded++;
  190. } else if (search != null) {
  191. existing.add(message.id);
  192. db.message().setMessageFound(message.id, true);
  193. }
  194. } catch (MessageRemovedException ex) {
  195. Log.w(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
  196. } catch (FolderClosedException ex) {
  197. throw ex;
  198. } catch (FolderClosedIOException ex) {
  199. throw ex;
  200. } catch (Throwable ex) {
  201. Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
  202. } finally {
  203. ((IMAPMessage) isub[j]).invalidateHeaders();
  204. }
  205. db.setTransactionSuccessful();
  206. } finally {
  207. db.endTransaction();
  208. }
  209. }
  210. Log.i(Helper.TAG, "Boundary done");
  211. } catch (final Throwable ex) {
  212. Log.e(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
  213. handler.post(new Runnable() {
  214. @Override
  215. public void run() {
  216. intf.onError(context, ex);
  217. }
  218. });
  219. } finally {
  220. searching = false;
  221. handler.post(new Runnable() {
  222. @Override
  223. public void run() {
  224. intf.onLoaded();
  225. }
  226. });
  227. }
  228. }
  229. });
  230. }
  231. }