1
0
mirror of https://github.com/moparisthebest/k-9 synced 2025-01-10 13:18:09 -05:00

using sync mode, instead of GetItemEstimate, which is not reliable

working read/unread sync
adapters cleanups
This commit is contained in:
Vitaly Polonetsky 2011-07-26 00:57:27 +03:00 committed by Jesse Vincent
parent 308ee4c1f2
commit 8bf95c1244
9 changed files with 641 additions and 1238 deletions

View File

@ -940,6 +940,8 @@ public class MessagingController implements Runnable {
* Get the remote message count.
*/
int remoteMessageCount = remoteFolder.getMessageCount();
boolean syncMode = remoteFolder.isSyncMode();
int visibleLimit = localFolder.getVisibleLimit();
@ -956,7 +958,7 @@ public class MessagingController implements Runnable {
final Date earliestDate = account.getEarliestPollDate();
if (remoteMessageCount > 0) {
if (remoteMessageCount > 0 || syncMode) {
/* Message numbers start at 1. */
int remoteStart;
if (visibleLimit > 0) {

View File

@ -22,21 +22,17 @@ import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import android.content.Context;
import android.text.TextUtils;
@ -54,15 +50,11 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Folder.OpenMode;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.store.exchange.Eas;
import com.fsck.k9.mail.store.exchange.adapter.AbstractSyncAdapter;
import com.fsck.k9.mail.store.exchange.adapter.AccountSyncAdapter;
import com.fsck.k9.mail.store.exchange.adapter.EmailSyncAdapter;
import com.fsck.k9.mail.store.exchange.adapter.EasEmailSyncParser;
import com.fsck.k9.mail.store.exchange.adapter.FolderSyncParser;
import com.fsck.k9.mail.store.exchange.adapter.GetItemEstimateParser;
import com.fsck.k9.mail.store.exchange.adapter.ProvisionParser;
import com.fsck.k9.mail.store.exchange.adapter.Serializer;
import com.fsck.k9.mail.store.exchange.adapter.Tags;
@ -93,6 +85,8 @@ public class EasStore extends Store {
// IOException is thrown. After a small added allowance, our watchdog alarm goes off (allowing
// us to detect a silently dropped connection). The allowance is defined below.
static private final int COMMAND_TIMEOUT = 30*1000;
// Connection timeout is the time given to connect to the server before reporting an IOException
static private final int CONNECTION_TIMEOUT = 20*1000;
// This needs to be long enough to send the longest reasonable message, without being so long
// as to effectively "hang" sending of mail. The standard 30 second timeout isn't long enough
@ -132,9 +126,7 @@ public class EasStore extends Store {
private boolean mSecure;
private HttpClient mHttpClient = null;
private HttpContext mContext = null;
private String mAuthString;
private CookieStore mAuthCookies = null;
private Folder mSendFolder = null;
private HashMap<String, EasFolder> mFolderList = new HashMap<String, EasFolder>();
@ -297,7 +289,6 @@ public class EasStore extends Store {
// account.setName("%TestAccount%");
// account.setStoreUri(mUri.toString());
EasStore svc = new EasStore(mAccount);
svc.mContext = mContext;
svc.mHost = hostAddress;
svc.mUsername = userName;
svc.mPassword = password;
@ -531,6 +522,7 @@ public class EasStore extends Store {
*/
protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout,
boolean isPingCommand) throws IOException {
HttpConnectionParams.setSoTimeout(method.getParams(), timeout);
// synchronized(getSynchronizer()) {
// mPendingPost = method;
// long alarmTime = timeout + WATCHDOG_TIMEOUT_ALLOWANCE;
@ -590,10 +582,13 @@ public class EasStore extends Store {
String us = makeUriString("OPTIONS", null);
HttpOptions method = new HttpOptions(URI.create(us));
setHeaders(method, false);
HttpConnectionParams.setSoTimeout(method.getParams(), COMMAND_TIMEOUT);
return client.execute(method);
}
/**
/**
* Set standard HTTP headers, using a policy key if required
* @param method the method we are going to send
* @param usePolicyKey whether or not a policy key should be sent in the headers
@ -704,7 +699,7 @@ public class EasStore extends Store {
if (len != 0) {
InputStream is = entity.getContent();
// Returns true if we need to sync again
if (new FolderSyncParser(is, new AccountSyncAdapter(mAccount), this, folderList)
if (new FolderSyncParser(is, this, folderList)
.parse()) {
throw new RuntimeException();
}
@ -784,10 +779,6 @@ public class EasStore extends Store {
* Authentication related methods
*/
public CookieStore getAuthCookies() {
return mAuthCookies;
}
public String getAlias() {
return mAlias;
}
@ -799,13 +790,14 @@ public class EasStore extends Store {
public HttpClient getHttpClient() throws MessagingException {
if (mHttpClient == null) {
mHttpClient = new DefaultHttpClient();
HttpParams params = mHttpClient.getParams();
// Disable automatDic redirects on the http client.
mHttpClient.getParams().setBooleanParameter("http.protocol.handle-redirects", false);
params.setBooleanParameter("http.protocol.handle-redirects", false);
// Setup a cookie store for forms-based authentication.
mContext = new BasicHttpContext();
mAuthCookies = new BasicCookieStore();
mContext.setAttribute(ClientContext.COOKIE_STORE, mAuthCookies);
HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
HttpConnectionParams.setSocketBufferSize(params, 8192);
SchemeRegistry reg = mHttpClient.getConnectionManager().getSchemeRegistry();
try {
@ -898,9 +890,17 @@ public class EasStore extends Store {
this.mType = type;
}
public String getSyncKey() {
public String getSyncKeyUnmodified() {
return mSyncKey;
}
public String getSyncKey() {
if (mSyncKey == null) {
Log.d(K9.LOG_TAG, "Reset SyncKey to 0");
mSyncKey = "0";
}
return mSyncKey;
}
public void setSyncKey(String mSyncKey) {
this.mSyncKey = mSyncKey;
@ -964,45 +964,12 @@ public class EasStore extends Store {
// processRequest(mFolderUrl, action, messageBody, headers, false);
}
private int getMessageCount(boolean read) throws MessagingException {
Serializer s = new Serializer();
try {
s
.start(Tags.GIE_GET_ITEM_ESTIMATE)
.start(Tags.GIE_COLLECTIONS)
.start(Tags.GIE_COLLECTION)
.data(Tags.SYNC_SYNC_KEY, "0")
.data(Tags.GIE_COLLECTION_ID, mServerId)
.end()
.end()
.end()
.done();
HttpResponse resp = sendHttpClientPost("GetItemEstimate", s.toByteArray());
int code = resp.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_OK) {
HttpEntity entity = resp.getEntity();
int len = (int)entity.getContentLength();
if (len != 0) {
InputStream is = entity.getContent();
GetItemEstimateParser gieParser = new GetItemEstimateParser(is);
if (gieParser.parse()) {
return gieParser.getEstimate();
}
}
}
// On failures
throw new MessagingException("getItemEstimate call returned not OK status");
} catch (IOException e) {
throw new MessagingException("getItemEstimate call failed", e);
}
}
@Override
public int getMessageCount() throws MessagingException {
open(OpenMode.READ_WRITE);
this.mMessageCount = getMessageCount(true);
return this.mMessageCount;
return -1;
// this.mMessageCount = getMessageCount(true);
// return this.mMessageCount;
}
@Override
@ -1060,9 +1027,9 @@ public class EasStore extends Store {
public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
throws MessagingException {
try {
EmailSyncAdapter target = getMessagesInternal(null, null, null, start, end);
EasEmailSyncParser syncParser = getMessagesInternal(null, null, null, start, end);
List<EasMessage> messages = target.getMessages();
List<EasMessage> messages = syncParser.getMessages();
return messages.toArray(EMPTY_MESSAGE_ARRAY);
} catch (IOException e) {
@ -1070,15 +1037,16 @@ public class EasStore extends Store {
}
}
private EmailSyncAdapter getMessagesInternal(Message[] messages, FetchProfile fp, MessageRetrievalListener listener,
private EasEmailSyncParser getMessagesInternal(Message[] messages, FetchProfile fp, MessageRetrievalListener listener,
int start, int end) throws IOException,
MessagingException {
Serializer s = new Serializer();
EmailSyncAdapter target = new EmailSyncAdapter(this, mAccount);
EasEmailSyncParser syncParser = null;
// EmailSyncAdapter target = new EmailSyncAdapter(this, mAccount);
String className = target.getCollectionName();
String syncKey = target.getSyncKey();
String className = "Email";
String syncKey = getSyncKey();
// userLog("sync, sending ", className, " syncKey: ", syncKey);
s.start(Tags.SYNC_SYNC)
.start(Tags.SYNC_COLLECTIONS)
@ -1102,7 +1070,7 @@ public class EasStore extends Store {
if (messages == null) {
s.tag(Tags.SYNC_GET_CHANGES);
s.data(Tags.SYNC_WINDOW_SIZE, Integer.toString(end - start + 1));
// s.data(Tags.SYNC_WINDOW_SIZE, Integer.toString(end - start + 1));
}
// Handle options
s.start(Tags.SYNC_OPTIONS);
@ -1162,22 +1130,10 @@ public class EasStore extends Store {
if (code == HttpStatus.SC_OK) {
InputStream is = resp.getEntity().getContent();
if (is != null) {
boolean moreAvailable = target.parse(is);
// int loopingCount = 0;
// if (target.isLooping()) {
// loopingCount ++;
// Log.d(K9.LOG_TAG, "** Looping: " + loopingCount);
// // After the maximum number of loops, we'll set moreAvailable to false and
// // allow the sync loop to terminate
// if (moreAvailable && (loopingCount > MAX_LOOPING_COUNT)) {
// Log.d(K9.LOG_TAG, "** Looping force stopped");
// moreAvailable = false;
// }
// } else {
// loopingCount = 0;
// }
target.cleanup();
syncParser = new EasEmailSyncParser(is, this, mAccount);
boolean moreAvailable = syncParser.parse();
if (moreAvailable && syncKey.equals("0")) {
return getMessagesInternal(messages, fp, listener, start, end);
}
@ -1196,7 +1152,7 @@ public class EasStore extends Store {
// return;
throw new MessagingException("not ok status");
}
return target;
return syncParser;
}
private String getEmailFilter() {
@ -1280,8 +1236,8 @@ public class EasStore extends Store {
boolean fetchBody = fp.contains(FetchProfile.Item.BODY);
if (fetchBodySane || fetchBody) {
try {
EmailSyncAdapter target = getMessagesInternal(messages, fp, listener, -1, -1);
messages = target.getMessages().toArray(EMPTY_MESSAGE_ARRAY);
EasEmailSyncParser syncParser = getMessagesInternal(messages, fp, listener, -1, -1);
messages = syncParser.getMessages().toArray(EMPTY_MESSAGE_ARRAY);
} catch (IOException e) {
throw new MessagingException("io exception while fetching messages", e);
}
@ -1324,8 +1280,6 @@ public class EasStore extends Store {
}
private void markServerMessagesRead(final String[] uids, final boolean read) throws MessagingException {
EmailSyncAdapter target = new EmailSyncAdapter(this, mAccount);
new SyncCommand() {
@Override
void prepareCommand(Serializer s) throws IOException {
@ -1340,12 +1294,10 @@ public class EasStore extends Store {
}
s.end();
}
}.send(target, mServerId);
}.send(this);
}
private void deleteServerMessages(final String[] uids) throws MessagingException {
EmailSyncAdapter target = new EmailSyncAdapter(this, mAccount);
new SyncCommand() {
@Override
void prepareCommand(Serializer s) throws IOException {
@ -1359,7 +1311,7 @@ public class EasStore extends Store {
}
s.end();
}
}.send(target, mServerId);
}.send(this);
}
@Override
@ -1432,11 +1384,11 @@ public class EasStore extends Store {
private abstract class SyncCommand {
public void send(AbstractSyncAdapter target, String folderServerId) throws MessagingException {
public void send(EasFolder folder) throws MessagingException {
try {
int timeout = COMMAND_TIMEOUT;
byte[] byteArr = prepare(target, folderServerId);
byte[] byteArr = prepare(folder);
HttpResponse resp = sendHttpClientPost("Sync", new ByteArrayEntity(byteArr),
timeout);
@ -1444,7 +1396,8 @@ public class EasStore extends Store {
if (code == HttpStatus.SC_OK) {
InputStream is = resp.getEntity().getContent();
if (is != null) {
target.cleanup();
EasEmailSyncParser syncParser = new EasEmailSyncParser(is, folder, folder.getAccount());
parseResponse(syncParser, is);
} else {
Log.d(K9.LOG_TAG, "Empty input stream in sync command response");
}
@ -1456,17 +1409,18 @@ public class EasStore extends Store {
}
}
byte[] prepare(AbstractSyncAdapter target, String folderServerId) throws IOException {
byte[] prepare(EasFolder folder) throws IOException {
Serializer s = new Serializer();
String className = target.getCollectionName();
String syncKey = target.getSyncKey();
String className = "Email";
String syncKey = folder.getSyncKey();
String folderServerId = folder.mServerId;
s.start(Tags.SYNC_SYNC)
.start(Tags.SYNC_COLLECTIONS)
.start(Tags.SYNC_COLLECTION)
.data(Tags.SYNC_CLASS, className)
.data(Tags.SYNC_SYNC_KEY, syncKey)
.data(Tags.SYNC_COLLECTION_ID, folderServerId);
.data(Tags.SYNC_COLLECTION_ID, folderServerId );
prepareCommand(s);
@ -1477,6 +1431,10 @@ public class EasStore extends Store {
abstract void prepareCommand(Serializer s) throws IOException;
void parseResponse(EasEmailSyncParser syncParser, InputStream is) throws IOException, MessagingException {
syncParser.parse();
}
}
}

View File

@ -1,94 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fsck.k9.mail.store.exchange.adapter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import android.content.Context;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.EasStore.EasFolder;
/**
* Parent class of all sync adapters (EasMailbox, EasCalendar, and EasContacts)
*
*/
public abstract class AbstractSyncAdapter {
public static final int SECONDS = 1000;
public static final int MINUTES = SECONDS*60;
public static final int HOURS = MINUTES*60;
public static final int DAYS = HOURS*24;
public static final int WEEKS = DAYS*7;
public EasFolder mFolder;
public Context mContext;
public Account mAccount;
// Create the data for local changes that need to be sent up to the server
public abstract boolean sendLocalChanges(Serializer s)
throws IOException;
// Parse incoming data from the EAS server, creating, modifying, and deleting objects as
// required through the EmailProvider
public abstract boolean parse(InputStream is)
throws IOException, MessagingException;
// The name used to specify the collection type of the target (Email, Calendar, or Contacts)
public abstract String getCollectionName();
public abstract void cleanup();
public abstract boolean isSyncable();
public boolean isLooping() {
return false;
}
public AbstractSyncAdapter(EasFolder folder, Account account) {
mFolder = folder;
mAccount = account;
}
public void userLog(String ...strings) {
Log.i(K9.LOG_TAG, Arrays.toString(strings));
}
public void incrementChangeCount() {
// mService.mChangeCount++;
}
/**
* Returns the current SyncKey; override if the SyncKey is stored elsewhere (as for Contacts)
* @return the current SyncKey for the Mailbox
* @throws IOException
*/
public String getSyncKey() throws IOException {
if (mFolder.getSyncKey() == null) {
userLog("Reset SyncKey to 0");
mFolder.setSyncKey("0");
}
return mFolder.getSyncKey();
}
public void setSyncKey(String syncKey, boolean inCommands) throws IOException {
mFolder.setSyncKey(syncKey);
}
}

View File

@ -41,13 +41,11 @@ public abstract class AbstractSyncParser extends Parser {
protected EasFolder mFolder;
protected Account mAccount;
protected ContentResolver mContentResolver;
protected AbstractSyncAdapter mAdapter;
private boolean mLooping;
public AbstractSyncParser(InputStream in, AbstractSyncAdapter adapter, EasFolder folder, Account account) throws IOException {
public AbstractSyncParser(InputStream in, EasFolder folder, Account account) throws IOException {
super(in);
mAdapter = adapter;
mFolder = folder;
mAccount = account;
}
@ -112,7 +110,7 @@ public abstract class AbstractSyncParser extends Parser {
// Status = 3 means invalid sync key
if (status == 3) {
// Must delete all of the data and start over with syncKey of "0"
mAdapter.setSyncKey("0", false);
mFolder.setSyncKey("0");
// Make this a push box through the first sync
// TODO Make frequency conditional on user settings!
MailboxAdapter.mSyncInterval = MailboxAdapter.CHECK_INTERVAL_PUSH;
@ -134,14 +132,13 @@ public abstract class AbstractSyncParser extends Parser {
} else if (tag == Tags.SYNC_MORE_AVAILABLE) {
moreAvailable = true;
} else if (tag == Tags.SYNC_SYNC_KEY) {
if (mAdapter.getSyncKey().equals("0")) {
if (mFolder.getSyncKey().equals("0")) {
moreAvailable = true;
}
String newKey = getValue();
userLog("Parsed key for ", mFolder.toString(), ": ", newKey);
if (!newKey.equals(mFolder.getSyncKey())) {
mAdapter.setSyncKey(newKey, true);
// cv.put(MailboxColumns.SYNC_KEY, newKey);
mFolder.setSyncKey(newKey);
mailboxUpdated = true;
newSyncKey = true;
}
@ -203,4 +200,5 @@ public abstract class AbstractSyncParser extends Parser {
void userLog(String string, int num, String string2) {
Log.i(K9.LOG_TAG, string + num + string2);
}
}

View File

@ -1,37 +0,0 @@
package com.fsck.k9.mail.store.exchange.adapter;
import java.io.IOException;
import java.io.InputStream;
import com.fsck.k9.Account;
public class AccountSyncAdapter extends AbstractSyncAdapter {
public AccountSyncAdapter(Account account) {
super(null, account);
}
@Override
public void cleanup() {
}
@Override
public String getCollectionName() {
return null;
}
@Override
public boolean parse(InputStream is) throws IOException {
return false;
}
@Override
public boolean sendLocalChanges(Serializer s) throws IOException {
return false;
}
@Override
public boolean isSyncable() {
return true;
}
}

View File

@ -0,0 +1,564 @@
/**
*
*/
package com.fsck.k9.mail.store.exchange.adapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import android.webkit.MimeTypeMap;
import com.fsck.k9.Account;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.EasStore.EasFolder;
import com.fsck.k9.mail.store.EasStore.EasMessage;
import com.fsck.k9.mail.store.exchange.Eas;
public class EasEmailSyncParser extends AbstractSyncParser {
/**
*
*/
ArrayList<EasMessage> newEmails = new ArrayList<EasMessage>();
ArrayList<String> deletedEmails = new ArrayList<String>();
ArrayList<ServerChange> changedEmails = new ArrayList<ServerChange>();
public EasEmailSyncParser(InputStream in, EasFolder folder, Account account) throws IOException {
super(in, folder, account);
}
@Override
public void wipe() {
// mContentResolver.delete(Message.CONTENT_URI,
// Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
// mContentResolver.delete(Message.DELETED_CONTENT_URI,
// Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
// mContentResolver.delete(Message.UPDATED_CONTENT_URI,
// Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
}
public void addData (EasMessage msg) throws IOException, MessagingException {
// ArrayList<Attachment> atts = new ArrayList<Attachment>();
while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
switch (tag) {
case Tags.EMAIL_ATTACHMENTS:
case Tags.BASE_ATTACHMENTS: // BASE_ATTACHMENTS is used in EAS 12.0 and up
attachmentsParser(msg);
break;
case Tags.EMAIL_TO:
msg.setRecipients(RecipientType.TO, Address.parse(getValue()));
break;
case Tags.EMAIL_FROM:
Address[] froms = Address.parse(getValue());
if (froms != null && froms.length > 0) {
// msg.mDisplayName = froms[0].toFriendly();
msg.setFrom(froms[0]);
}
break;
case Tags.EMAIL_CC:
msg.setRecipients(RecipientType.CC, Address.parse(getValue()));
break;
case Tags.EMAIL_REPLY_TO:
msg.setReplyTo(Address.parse(getValue()));
break;
case Tags.EMAIL_DATE_RECEIVED:
getValue();
// Date receivedDate = Utility.parseEmailDateTimeToMillis(getValue());
// msg.setInternalDate(receivedDate);
break;
case Tags.EMAIL_SUBJECT:
msg.setSubject(getValue());
break;
case Tags.EMAIL_READ:
msg.setFlagInternal(Flag.SEEN, getValueInt() == 1);
break;
case Tags.BASE_BODY:
bodyParser(msg);
break;
case Tags.EMAIL_FLAG:
msg.setFlagInternal(Flag.FLAGGED, flagParser());
break;
case Tags.EMAIL_BODY:
String body = getValue();
InputStream bodyStream = new ByteArrayInputStream(body.getBytes());
try {
msg.setBody(MimeUtility.decodeBody(bodyStream, null));
} catch (MessagingException e) {
throw new IOException(e);
}
break;
case Tags.EMAIL_MESSAGE_CLASS:
String messageClass = getValue();
// if (messageClass.equals("IPM.Schedule.Meeting.Request")) {
// msg.mFlags |= Message.FLAG_INCOMING_MEETING_INVITE;
// } else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) {
// msg.mFlags |= Message.FLAG_INCOMING_MEETING_CANCEL;
// }
break;
case Tags.EMAIL_MEETING_REQUEST:
meetingRequestParser(msg);
break;
default:
skipTag();
}
}
// if (atts.size() > 0) {
// msg.mAttachments = atts;
// }
}
/**
* Set up the meetingInfo field in the message with various pieces of information gleaned
* from MeetingRequest tags. This information will be used later to generate an appropriate
* reply email if the user chooses to respond
* @param msg the Message being built
* @throws IOException
*/
private void meetingRequestParser(Message msg) throws IOException {
// PackedString.Builder packedString = new PackedString.Builder();
while (nextTag(Tags.EMAIL_MEETING_REQUEST) != END) {
String value;
switch (tag) {
case Tags.EMAIL_DTSTAMP:
value = getValue();
// packedString.put(MeetingInfo.MEETING_DTSTAMP, value);
break;
case Tags.EMAIL_START_TIME:
value = getValue();
// packedString.put(MeetingInfo.MEETING_DTSTART, value);
break;
case Tags.EMAIL_END_TIME:
value = getValue();
// packedString.put(MeetingInfo.MEETING_DTEND, value);
break;
case Tags.EMAIL_ORGANIZER:
value = getValue();
// packedString.put(MeetingInfo.MEETING_ORGANIZER_EMAIL, value);
break;
case Tags.EMAIL_LOCATION:
value = getValue();
// packedString.put(MeetingInfo.MEETING_LOCATION, value);
break;
case Tags.EMAIL_GLOBAL_OBJID:
value = getValue();
// packedString.put(MeetingInfo.MEETING_UID,
// CalendarUtilities.getUidFromGlobalObjId(value));
break;
case Tags.EMAIL_CATEGORIES:
nullParser();
break;
case Tags.EMAIL_RECURRENCES:
recurrencesParser();
break;
default:
skipTag();
}
}
// if (msg.mSubject != null) {
// packedString.put(MeetingInfo.MEETING_TITLE, msg.mSubject);
// }
// msg.mMeetingInfo = packedString.toString();
}
private void nullParser() throws IOException {
while (nextTag(Tags.EMAIL_CATEGORIES) != END) {
skipTag();
}
}
private void recurrencesParser() throws IOException {
while (nextTag(Tags.EMAIL_RECURRENCES) != END) {
switch (tag) {
case Tags.EMAIL_RECURRENCE:
nullParser();
break;
default:
skipTag();
}
}
}
private void addParser(ArrayList<EasMessage> emails) throws IOException, MessagingException {
EasMessage msg = new EasMessage(null, mFolder);
// msg.mAccountKey = mAccount.mId;
// msg.mMailboxKey = mMailbox.mId;
// msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
while (nextTag(Tags.SYNC_ADD) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
String serverId = getValue();
msg.setUid(serverId);
break;
case Tags.SYNC_APPLICATION_DATA:
addData(msg);
break;
default:
skipTag();
}
}
emails.add(msg);
}
private void fetchParser(ArrayList<EasMessage> emails) throws IOException, MessagingException {
EasMessage msg = new EasMessage(null, mFolder);
// msg.mAccountKey = mAccount.mId;
// msg.mMailboxKey = mMailbox.mId;
// msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
while (nextTag(Tags.SYNC_FETCH) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
String serverId = getValue();
msg.setUid(serverId);
break;
case Tags.SYNC_APPLICATION_DATA:
addData(msg);
break;
default:
skipTag();
}
}
emails.add(msg);
}
// For now, we only care about the "active" state
private Boolean flagParser() throws IOException {
Boolean state = false;
while (nextTag(Tags.EMAIL_FLAG) != END) {
switch (tag) {
case Tags.EMAIL_FLAG_STATUS:
state = getValueInt() == 2;
break;
default:
skipTag();
}
}
return state;
}
private void bodyParser(Message msg) throws IOException {
String bodyType = Eas.BODY_PREFERENCE_TEXT;
String body = "";
while (nextTag(Tags.EMAIL_BODY) != END) {
switch (tag) {
case Tags.BASE_TYPE:
bodyType = getValue();
break;
case Tags.BASE_DATA:
body = getValue();
break;
default:
skipTag();
}
}
// We always ask for TEXT or HTML; there's no third option
// if (bodyType.equals(Eas.BODY_PREFERENCE_HTML)) {
// msg.mHtml = body;
// } else {
// msg.mText = body;
// }
try {
InputStream bodyStream = new ByteArrayInputStream(body.getBytes());
//String contentTransferEncoding;
// contentTransferEncoding = msg.getHeader(
// MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
// msg.setBody(MimeUtility.decodeBody(bodyStream, contentTransferEncoding));
((EasMessage) msg).parse(bodyStream);
} catch (MessagingException e) {
throw new IOException(e);
}
}
private void attachmentsParser(Message msg) throws IOException {
while (nextTag(Tags.EMAIL_ATTACHMENTS) != END) {
switch (tag) {
case Tags.EMAIL_ATTACHMENT:
case Tags.BASE_ATTACHMENT: // BASE_ATTACHMENT is used in EAS 12.0 and up
attachmentParser(msg);
break;
default:
skipTag();
}
}
}
private void attachmentParser(Message msg) throws IOException {
String fileName = null;
String length = null;
String location = null;
while (nextTag(Tags.EMAIL_ATTACHMENT) != END) {
switch (tag) {
// We handle both EAS 2.5 and 12.0+ attachments here
case Tags.EMAIL_DISPLAY_NAME:
case Tags.BASE_DISPLAY_NAME:
fileName = getValue();
break;
case Tags.EMAIL_ATT_NAME:
case Tags.BASE_FILE_REFERENCE:
location = getValue();
break;
case Tags.EMAIL_ATT_SIZE:
case Tags.BASE_ESTIMATED_DATA_SIZE:
length = getValue();
break;
default:
skipTag();
}
}
if ((fileName != null) && (length != null) && (location != null)) {
// Attachment att = new Attachment();
// att.mEncoding = "base64";
// att.mSize = Long.parseLong(length);
// att.mFileName = fileName;
// att.mLocation = location;
// att.mMimeType = getMimeTypeFromFileName(fileName);
// atts.add(att);
// msg.mFlagAttachment = true;
}
}
/**
* Try to determine a mime type from a file name, defaulting to application/x, where x
* is either the extension or (if none) octet-stream
* At the moment, this is somewhat lame, since many file types aren't recognized
* @param fileName the file name to ponder
* @return
*/
// Note: The MimeTypeMap method currently uses a very limited set of mime types
// A bug has been filed against this issue.
public String getMimeTypeFromFileName(String fileName) {
String mimeType;
int lastDot = fileName.lastIndexOf('.');
String extension = null;
if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
extension = fileName.substring(lastDot + 1).toLowerCase();
}
if (extension == null) {
// A reasonable default for now.
mimeType = "application/octet-stream";
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType == null) {
mimeType = "application/" + extension;
}
}
return mimeType;
}
// private Cursor getServerIdCursor(String serverId, String[] projection) {
// mBindArguments[0] = serverId;
// mBindArguments[1] = mMailboxIdAsString;
// return mContentResolver.query(Message.CONTENT_URI, projection,
// WHERE_SERVER_ID_AND_MAILBOX_KEY, mBindArguments, null);
// }
/*package*/ void deleteParser(ArrayList<String> deletes, int entryTag) throws IOException {
while (nextTag(entryTag) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
String serverId = getValue();
deletes.add(serverId);
break;
default:
skipTag();
}
}
}
class ServerChange {
String serverId;
Boolean read;
Boolean flag;
ServerChange(String _serverId, Boolean _read, Boolean _flag) {
serverId = _serverId;
read = _read;
flag = _flag;
}
}
/*package*/ void changeParser(ArrayList<ServerChange> changes) throws IOException {
String serverId = null;
while (nextTag(Tags.SYNC_CHANGE) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
serverId = getValue();
break;
case Tags.SYNC_APPLICATION_DATA:
changeApplicationDataParser(changes, serverId);
break;
default:
skipTag();
}
}
}
private void changeApplicationDataParser(ArrayList<ServerChange> changes, String serverId) throws IOException {
Boolean read = null;
Boolean flag = null;
while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
switch (tag) {
case Tags.EMAIL_READ:
read = getValueInt() == 1;
break;
case Tags.EMAIL_FLAG:
flag = flagParser();
break;
default:
skipTag();
}
}
changes.add(new ServerChange(serverId, read, flag));
}
/* (non-Javadoc)
* @see com.android.exchange.adapter.EasContentParser#commandsParser()
*/
@Override
public void commandsParser() throws IOException, MessagingException {
while (nextTag(Tags.SYNC_COMMANDS) != END) {
if (tag == Tags.SYNC_ADD) {
addParser(newEmails);
// this.emailSyncAdapter.incrementChangeCount();
} else if (tag == Tags.SYNC_DELETE || tag == Tags.SYNC_SOFT_DELETE) {
deleteParser(deletedEmails, tag);
// this.emailSyncAdapter.incrementChangeCount();
} else if (tag == Tags.SYNC_CHANGE) {
changeParser(changedEmails);
// this.emailSyncAdapter.incrementChangeCount();
} else
skipTag();
}
}
@Override
public void responsesParser() throws IOException, MessagingException {
while (nextTag(Tags.SYNC_RESPONSES) != END) {
if (tag == Tags.SYNC_FETCH) {
fetchParser(newEmails);
// this.emailSyncAdapter.incrementChangeCount();
// } else if (tag == Tags.SYNC_ADD) {
// deleteParser(deletedEmails, tag);
// incrementChangeCount();
// } else if (tag == Tags.SYNC_CHANGE) {
// changeParser(changedEmails);
// incrementChangeCount();
} else
skipTag();
}
}
@Override
public void commit() {
// int notifyCount = 0;
//
// // Use a batch operation to handle the changes
// // TODO New mail notifications? Who looks for these?
// ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// for (Message msg: newEmails) {
// if (!msg.mFlagRead) {
// notifyCount++;
// }
// msg.addSaveOps(ops);
// }
// for (Long id : deletedEmails) {
// ops.add(ContentProviderOperation.newDelete(
// ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
// AttachmentProvider.deleteAllAttachmentFiles(mContext, mAccount.mId, id);
// }
// if (!changedEmails.isEmpty()) {
// // Server wins in a conflict...
// for (ServerChange change : changedEmails) {
// ContentValues cv = new ContentValues();
// if (change.read != null) {
// cv.put(MessageColumns.FLAG_READ, change.read);
// }
// if (change.flag != null) {
// cv.put(MessageColumns.FLAG_FAVORITE, change.flag);
// }
// ops.add(ContentProviderOperation.newUpdate(
// ContentUris.withAppendedId(Message.CONTENT_URI, change.id))
// .withValues(cv)
// .build());
// }
// }
//
// // We only want to update the sync key here
// ContentValues mailboxValues = new ContentValues();
// mailboxValues.put(Mailbox.SYNC_KEY, mMailbox.mSyncKey);
// ops.add(ContentProviderOperation.newUpdate(
// ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId))
// .withValues(mailboxValues).build());
//
// addCleanupOps(ops);
//
// // No commits if we're stopped
// synchronized (mService.getSynchronizer()) {
// if (mService.isStopped()) return;
// try {
// mContentResolver.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
// userLog(mMailbox.mDisplayName, " SyncKey saved as: ", mMailbox.mSyncKey);
// } catch (RemoteException e) {
// // There is nothing to be done here; fail by returning null
// } catch (OperationApplicationException e) {
// // There is nothing to be done here; fail by returning null
// }
// }
//
// if (notifyCount > 0) {
// // Use the new atomic add URI in EmailProvider
// // We could add this to the operations being done, but it's not strictly
// // speaking necessary, as the previous batch preserves the integrity of the
// // database, whereas this is purely for notification purposes, and is itself atomic
// ContentValues cv = new ContentValues();
// cv.put(EmailContent.FIELD_COLUMN_NAME, AccountColumns.NEW_MESSAGE_COUNT);
// cv.put(EmailContent.ADD_COLUMN_NAME, notifyCount);
// Uri uri = ContentUris.withAppendedId(Account.ADD_TO_FIELD_URI, mAccount.mId);
// mContentResolver.update(uri, cv, null, null);
// MailService.actionNotifyNewMessages(mContext, mAccount.mId);
// }
}
public List<EasMessage> getMessages() throws MessagingException {
List<EasMessage> messages = new ArrayList<EasMessage>();
messages.addAll(newEmails);
for (ServerChange srvChg : changedEmails) {
EasMessage msg = new EasMessage(srvChg.serverId, mFolder);
if (srvChg.read != null) {
msg.setFlag(Flag.SEEN, srvChg.read);
}
if (srvChg.flag != null) {
msg.setFlag(Flag.FLAGGED, srvChg.flag);
}
messages.add(msg);
}
for (String serverId : deletedEmails) {
EasMessage msg = new EasMessage(serverId, mFolder);
msg.setFlag(Flag.DELETED, true);
messages.add(msg);
}
return messages;
}
}

View File

@ -1,905 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fsck.k9.mail.store.exchange.adapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import android.webkit.MimeTypeMap;
import com.fsck.k9.Account;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.EasStore.EasFolder;
import com.fsck.k9.mail.store.EasStore.EasMessage;
import com.fsck.k9.mail.store.exchange.Eas;
/**
* Sync adapter for EAS email
*
*/
public class EmailSyncAdapter extends AbstractSyncAdapter {
// private static final int UPDATES_READ_COLUMN = 0;
// private static final int UPDATES_MAILBOX_KEY_COLUMN = 1;
// private static final int UPDATES_SERVER_ID_COLUMN = 2;
// private static final int UPDATES_FLAG_COLUMN = 3;
// private static final String[] UPDATES_PROJECTION =
// {MessageColumns.FLAG_READ, MessageColumns.MAILBOX_KEY, SyncColumns.SERVER_ID,
// MessageColumns.FLAG_FAVORITE};
//
// private static final int MESSAGE_ID_SUBJECT_ID_COLUMN = 0;
// private static final int MESSAGE_ID_SUBJECT_SUBJECT_COLUMN = 1;
// private static final String[] MESSAGE_ID_SUBJECT_PROJECTION =
// new String[] { Message.RECORD_ID, MessageColumns.SUBJECT };
//
// private static final String WHERE_BODY_SOURCE_MESSAGE_KEY = Body.SOURCE_MESSAGE_KEY + "=?";
String[] mBindArguments = new String[2];
String[] mBindArgument = new String[1];
ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
// Holds the parser's value for isLooping()
boolean mIsLooping = false;
private List<EasMessage> newEmails;
public EmailSyncAdapter(EasFolder folder, Account account) {
super(folder, account);
}
@Override
public boolean parse(InputStream is) throws IOException, MessagingException {
EasEmailSyncParser p = new EasEmailSyncParser(is, this, mFolder, mAccount);
boolean res = p.parse();
// Hold on to the parser's value for isLooping() to pass back to the service
mIsLooping = p.isLooping();
newEmails = p.newEmails;
return res;
}
/**
* Return the value of isLooping() as returned from the parser
*/
@Override
public boolean isLooping() {
return mIsLooping;
}
@Override
public boolean isSyncable() {
return true;
}
public class EasEmailSyncParser extends AbstractSyncParser {
// private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY =
// SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?";
// private String mMailboxIdAsString;
ArrayList<EasMessage> newEmails = new ArrayList<EasMessage>();
ArrayList<Long> deletedEmails = new ArrayList<Long>();
ArrayList<ServerChange> changedEmails = new ArrayList<ServerChange>();
public EasEmailSyncParser(InputStream in, EmailSyncAdapter adapter, EasFolder folder, Account account) throws IOException {
super(in, adapter, folder, account);
// mMailboxIdAsString = Long.toString(mMailbox.mId);
}
@Override
public void wipe() {
// mContentResolver.delete(Message.CONTENT_URI,
// Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
// mContentResolver.delete(Message.DELETED_CONTENT_URI,
// Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
// mContentResolver.delete(Message.UPDATED_CONTENT_URI,
// Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
}
public void addData (EasMessage msg) throws IOException, MessagingException {
// ArrayList<Attachment> atts = new ArrayList<Attachment>();
while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
switch (tag) {
case Tags.EMAIL_ATTACHMENTS:
case Tags.BASE_ATTACHMENTS: // BASE_ATTACHMENTS is used in EAS 12.0 and up
attachmentsParser(msg);
break;
case Tags.EMAIL_TO:
msg.setRecipients(RecipientType.TO, Address.parse(getValue()));
break;
case Tags.EMAIL_FROM:
Address[] froms = Address.parse(getValue());
if (froms != null && froms.length > 0) {
// msg.mDisplayName = froms[0].toFriendly();
msg.setFrom(froms[0]);
}
break;
case Tags.EMAIL_CC:
msg.setRecipients(RecipientType.CC, Address.parse(getValue()));
break;
case Tags.EMAIL_REPLY_TO:
msg.setReplyTo(Address.parse(getValue()));
break;
case Tags.EMAIL_DATE_RECEIVED:
getValue();
// Date receivedDate = Utility.parseEmailDateTimeToMillis(getValue());
// msg.setInternalDate(receivedDate);
break;
case Tags.EMAIL_SUBJECT:
msg.setSubject(getValue());
break;
case Tags.EMAIL_READ:
msg.setFlagInternal(Flag.SEEN, getValueInt() == 1);
break;
case Tags.BASE_BODY:
bodyParser(msg);
break;
case Tags.EMAIL_FLAG:
msg.setFlagInternal(Flag.FLAGGED, flagParser());
break;
case Tags.EMAIL_BODY:
String body = getValue();
InputStream bodyStream = new ByteArrayInputStream(body.getBytes());
try {
msg.setBody(MimeUtility.decodeBody(bodyStream, null));
} catch (MessagingException e) {
throw new IOException(e);
}
break;
case Tags.EMAIL_MESSAGE_CLASS:
String messageClass = getValue();
// if (messageClass.equals("IPM.Schedule.Meeting.Request")) {
// msg.mFlags |= Message.FLAG_INCOMING_MEETING_INVITE;
// } else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) {
// msg.mFlags |= Message.FLAG_INCOMING_MEETING_CANCEL;
// }
break;
case Tags.EMAIL_MEETING_REQUEST:
meetingRequestParser(msg);
break;
default:
skipTag();
}
}
// if (atts.size() > 0) {
// msg.mAttachments = atts;
// }
}
/**
* Set up the meetingInfo field in the message with various pieces of information gleaned
* from MeetingRequest tags. This information will be used later to generate an appropriate
* reply email if the user chooses to respond
* @param msg the Message being built
* @throws IOException
*/
private void meetingRequestParser(Message msg) throws IOException {
// PackedString.Builder packedString = new PackedString.Builder();
while (nextTag(Tags.EMAIL_MEETING_REQUEST) != END) {
String value;
switch (tag) {
case Tags.EMAIL_DTSTAMP:
value = getValue();
// packedString.put(MeetingInfo.MEETING_DTSTAMP, value);
break;
case Tags.EMAIL_START_TIME:
value = getValue();
// packedString.put(MeetingInfo.MEETING_DTSTART, value);
break;
case Tags.EMAIL_END_TIME:
value = getValue();
// packedString.put(MeetingInfo.MEETING_DTEND, value);
break;
case Tags.EMAIL_ORGANIZER:
value = getValue();
// packedString.put(MeetingInfo.MEETING_ORGANIZER_EMAIL, value);
break;
case Tags.EMAIL_LOCATION:
value = getValue();
// packedString.put(MeetingInfo.MEETING_LOCATION, value);
break;
case Tags.EMAIL_GLOBAL_OBJID:
value = getValue();
// packedString.put(MeetingInfo.MEETING_UID,
// CalendarUtilities.getUidFromGlobalObjId(value));
break;
case Tags.EMAIL_CATEGORIES:
nullParser();
break;
case Tags.EMAIL_RECURRENCES:
recurrencesParser();
break;
default:
skipTag();
}
}
// if (msg.mSubject != null) {
// packedString.put(MeetingInfo.MEETING_TITLE, msg.mSubject);
// }
// msg.mMeetingInfo = packedString.toString();
}
private void nullParser() throws IOException {
while (nextTag(Tags.EMAIL_CATEGORIES) != END) {
skipTag();
}
}
private void recurrencesParser() throws IOException {
while (nextTag(Tags.EMAIL_RECURRENCES) != END) {
switch (tag) {
case Tags.EMAIL_RECURRENCE:
nullParser();
break;
default:
skipTag();
}
}
}
private void addParser(ArrayList<EasMessage> emails) throws IOException, MessagingException {
EasMessage msg = new EasMessage(null, mFolder);
// msg.mAccountKey = mAccount.mId;
// msg.mMailboxKey = mMailbox.mId;
// msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
while (nextTag(Tags.SYNC_ADD) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
String serverId = getValue();
msg.setUid(serverId);
break;
case Tags.SYNC_APPLICATION_DATA:
addData(msg);
break;
default:
skipTag();
}
}
emails.add(msg);
}
private void fetchParser(ArrayList<EasMessage> emails) throws IOException, MessagingException {
EasMessage msg = new EasMessage(null, mFolder);
// msg.mAccountKey = mAccount.mId;
// msg.mMailboxKey = mMailbox.mId;
// msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
while (nextTag(Tags.SYNC_FETCH) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
String serverId = getValue();
msg.setUid(serverId);
break;
case Tags.SYNC_APPLICATION_DATA:
addData(msg);
break;
default:
skipTag();
}
}
emails.add(msg);
}
// For now, we only care about the "active" state
private Boolean flagParser() throws IOException {
Boolean state = false;
while (nextTag(Tags.EMAIL_FLAG) != END) {
switch (tag) {
case Tags.EMAIL_FLAG_STATUS:
state = getValueInt() == 2;
break;
default:
skipTag();
}
}
return state;
}
private void bodyParser(Message msg) throws IOException {
String bodyType = Eas.BODY_PREFERENCE_TEXT;
String body = "";
while (nextTag(Tags.EMAIL_BODY) != END) {
switch (tag) {
case Tags.BASE_TYPE:
bodyType = getValue();
break;
case Tags.BASE_DATA:
body = getValue();
break;
default:
skipTag();
}
}
// We always ask for TEXT or HTML; there's no third option
// if (bodyType.equals(Eas.BODY_PREFERENCE_HTML)) {
// msg.mHtml = body;
// } else {
// msg.mText = body;
// }
try {
InputStream bodyStream = new ByteArrayInputStream(body.getBytes());
//String contentTransferEncoding;
// contentTransferEncoding = msg.getHeader(
// MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
// msg.setBody(MimeUtility.decodeBody(bodyStream, contentTransferEncoding));
((EasMessage) msg).parse(bodyStream);
} catch (MessagingException e) {
throw new IOException(e);
}
}
private void attachmentsParser(Message msg) throws IOException {
while (nextTag(Tags.EMAIL_ATTACHMENTS) != END) {
switch (tag) {
case Tags.EMAIL_ATTACHMENT:
case Tags.BASE_ATTACHMENT: // BASE_ATTACHMENT is used in EAS 12.0 and up
attachmentParser(msg);
break;
default:
skipTag();
}
}
}
private void attachmentParser(Message msg) throws IOException {
String fileName = null;
String length = null;
String location = null;
while (nextTag(Tags.EMAIL_ATTACHMENT) != END) {
switch (tag) {
// We handle both EAS 2.5 and 12.0+ attachments here
case Tags.EMAIL_DISPLAY_NAME:
case Tags.BASE_DISPLAY_NAME:
fileName = getValue();
break;
case Tags.EMAIL_ATT_NAME:
case Tags.BASE_FILE_REFERENCE:
location = getValue();
break;
case Tags.EMAIL_ATT_SIZE:
case Tags.BASE_ESTIMATED_DATA_SIZE:
length = getValue();
break;
default:
skipTag();
}
}
if ((fileName != null) && (length != null) && (location != null)) {
// Attachment att = new Attachment();
// att.mEncoding = "base64";
// att.mSize = Long.parseLong(length);
// att.mFileName = fileName;
// att.mLocation = location;
// att.mMimeType = getMimeTypeFromFileName(fileName);
// atts.add(att);
// msg.mFlagAttachment = true;
}
}
/**
* Try to determine a mime type from a file name, defaulting to application/x, where x
* is either the extension or (if none) octet-stream
* At the moment, this is somewhat lame, since many file types aren't recognized
* @param fileName the file name to ponder
* @return
*/
// Note: The MimeTypeMap method currently uses a very limited set of mime types
// A bug has been filed against this issue.
public String getMimeTypeFromFileName(String fileName) {
String mimeType;
int lastDot = fileName.lastIndexOf('.');
String extension = null;
if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
extension = fileName.substring(lastDot + 1).toLowerCase();
}
if (extension == null) {
// A reasonable default for now.
mimeType = "application/octet-stream";
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType == null) {
mimeType = "application/" + extension;
}
}
return mimeType;
}
// private Cursor getServerIdCursor(String serverId, String[] projection) {
// mBindArguments[0] = serverId;
// mBindArguments[1] = mMailboxIdAsString;
// return mContentResolver.query(Message.CONTENT_URI, projection,
// WHERE_SERVER_ID_AND_MAILBOX_KEY, mBindArguments, null);
// }
/*package*/ void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException {
while (nextTag(entryTag) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
String serverId = getValue();
// Find the message in this mailbox with the given serverId
// Cursor c = getServerIdCursor(serverId, MESSAGE_ID_SUBJECT_PROJECTION);
// try {
// if (c.moveToFirst()) {
// deletes.add(c.getLong(MESSAGE_ID_SUBJECT_ID_COLUMN));
// if (Eas.USER_LOG) {
// userLog("Deleting ", serverId + ", "
// + c.getString(MESSAGE_ID_SUBJECT_SUBJECT_COLUMN));
// }
// }
// } finally {
// c.close();
// }
break;
default:
skipTag();
}
}
}
class ServerChange {
long id;
Boolean read;
Boolean flag;
ServerChange(long _id, Boolean _read, Boolean _flag) {
id = _id;
read = _read;
flag = _flag;
}
}
/*package*/ void changeParser(ArrayList<ServerChange> changes) throws IOException {
String serverId = null;
Boolean oldRead = false;
Boolean oldFlag = false;
long id = 0;
while (nextTag(Tags.SYNC_CHANGE) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
serverId = getValue();
// Cursor c = getServerIdCursor(serverId, Message.LIST_PROJECTION);
// try {
// if (c.moveToFirst()) {
// userLog("Changing ", serverId);
// oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
// oldFlag = c.getInt(Message.LIST_FAVORITE_COLUMN) == 1;
// id = c.getLong(Message.LIST_ID_COLUMN);
// }
// } finally {
// c.close();
// }
break;
case Tags.SYNC_APPLICATION_DATA:
changeApplicationDataParser(changes, oldRead, oldFlag, id);
break;
default:
skipTag();
}
}
}
private void changeApplicationDataParser(ArrayList<ServerChange> changes, Boolean oldRead,
Boolean oldFlag, long id) throws IOException {
Boolean read = null;
Boolean flag = null;
while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
switch (tag) {
case Tags.EMAIL_READ:
read = getValueInt() == 1;
break;
case Tags.EMAIL_FLAG:
flag = flagParser();
break;
default:
skipTag();
}
}
if (((read != null) && !oldRead.equals(read)) ||
((flag != null) && !oldFlag.equals(flag))) {
changes.add(new ServerChange(id, read, flag));
}
}
/* (non-Javadoc)
* @see com.android.exchange.adapter.EasContentParser#commandsParser()
*/
@Override
public void commandsParser() throws IOException, MessagingException {
while (nextTag(Tags.SYNC_COMMANDS) != END) {
if (tag == Tags.SYNC_ADD) {
addParser(newEmails);
incrementChangeCount();
} else if (tag == Tags.SYNC_DELETE || tag == Tags.SYNC_SOFT_DELETE) {
deleteParser(deletedEmails, tag);
incrementChangeCount();
} else if (tag == Tags.SYNC_CHANGE) {
changeParser(changedEmails);
incrementChangeCount();
} else
skipTag();
}
}
@Override
public void responsesParser() throws IOException, MessagingException {
while (nextTag(Tags.SYNC_RESPONSES) != END) {
if (tag == Tags.SYNC_FETCH) {
fetchParser(newEmails);
incrementChangeCount();
// } else if (tag == Tags.SYNC_ADD) {
// deleteParser(deletedEmails, tag);
// incrementChangeCount();
// } else if (tag == Tags.SYNC_CHANGE) {
// changeParser(changedEmails);
// incrementChangeCount();
} else
skipTag();
}
}
@Override
public void commit() {
// int notifyCount = 0;
//
// // Use a batch operation to handle the changes
// // TODO New mail notifications? Who looks for these?
// ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// for (Message msg: newEmails) {
// if (!msg.mFlagRead) {
// notifyCount++;
// }
// msg.addSaveOps(ops);
// }
// for (Long id : deletedEmails) {
// ops.add(ContentProviderOperation.newDelete(
// ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
// AttachmentProvider.deleteAllAttachmentFiles(mContext, mAccount.mId, id);
// }
// if (!changedEmails.isEmpty()) {
// // Server wins in a conflict...
// for (ServerChange change : changedEmails) {
// ContentValues cv = new ContentValues();
// if (change.read != null) {
// cv.put(MessageColumns.FLAG_READ, change.read);
// }
// if (change.flag != null) {
// cv.put(MessageColumns.FLAG_FAVORITE, change.flag);
// }
// ops.add(ContentProviderOperation.newUpdate(
// ContentUris.withAppendedId(Message.CONTENT_URI, change.id))
// .withValues(cv)
// .build());
// }
// }
//
// // We only want to update the sync key here
// ContentValues mailboxValues = new ContentValues();
// mailboxValues.put(Mailbox.SYNC_KEY, mMailbox.mSyncKey);
// ops.add(ContentProviderOperation.newUpdate(
// ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId))
// .withValues(mailboxValues).build());
//
// addCleanupOps(ops);
//
// // No commits if we're stopped
// synchronized (mService.getSynchronizer()) {
// if (mService.isStopped()) return;
// try {
// mContentResolver.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
// userLog(mMailbox.mDisplayName, " SyncKey saved as: ", mMailbox.mSyncKey);
// } catch (RemoteException e) {
// // There is nothing to be done here; fail by returning null
// } catch (OperationApplicationException e) {
// // There is nothing to be done here; fail by returning null
// }
// }
//
// if (notifyCount > 0) {
// // Use the new atomic add URI in EmailProvider
// // We could add this to the operations being done, but it's not strictly
// // speaking necessary, as the previous batch preserves the integrity of the
// // database, whereas this is purely for notification purposes, and is itself atomic
// ContentValues cv = new ContentValues();
// cv.put(EmailContent.FIELD_COLUMN_NAME, AccountColumns.NEW_MESSAGE_COUNT);
// cv.put(EmailContent.ADD_COLUMN_NAME, notifyCount);
// Uri uri = ContentUris.withAppendedId(Account.ADD_TO_FIELD_URI, mAccount.mId);
// mContentResolver.update(uri, cv, null, null);
// MailService.actionNotifyNewMessages(mContext, mAccount.mId);
// }
}
}
@Override
public String getCollectionName() {
return "Email";
}
// private void addCleanupOps(ArrayList<ContentProviderOperation> ops) {
// // If we've sent local deletions, clear out the deleted table
// for (Long id: mDeletedIdList) {
// ops.add(ContentProviderOperation.newDelete(
// ContentUris.withAppendedId(Message.DELETED_CONTENT_URI, id)).build());
// }
// // And same with the updates
// for (Long id: mUpdatedIdList) {
// ops.add(ContentProviderOperation.newDelete(
// ContentUris.withAppendedId(Message.UPDATED_CONTENT_URI, id)).build());
// }
// }
//
@Override
public void cleanup() {
// if (!mDeletedIdList.isEmpty() || !mUpdatedIdList.isEmpty()) {
// ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// addCleanupOps(ops);
// try {
// mContext.getContentResolver()
// .applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
// } catch (RemoteException e) {
// // There is nothing to be done here; fail by returning null
// } catch (OperationApplicationException e) {
// // There is nothing to be done here; fail by returning null
// }
// }
}
private String formatTwo(int num) {
if (num < 10) {
return "0" + (char)('0' + num);
} else
return Integer.toString(num);
}
/**
* Create date/time in RFC8601 format. Oddly enough, for calendar date/time, Microsoft uses
* a different format that excludes the punctuation (this is why I'm not putting this in a
* parent class)
*/
public String formatDateTime(Calendar calendar) {
StringBuilder sb = new StringBuilder();
//YYYY-MM-DDTHH:MM:SS.MSSZ
sb.append(calendar.get(Calendar.YEAR));
sb.append('-');
sb.append(formatTwo(calendar.get(Calendar.MONTH) + 1));
sb.append('-');
sb.append(formatTwo(calendar.get(Calendar.DAY_OF_MONTH)));
sb.append('T');
sb.append(formatTwo(calendar.get(Calendar.HOUR_OF_DAY)));
sb.append(':');
sb.append(formatTwo(calendar.get(Calendar.MINUTE)));
sb.append(':');
sb.append(formatTwo(calendar.get(Calendar.SECOND)));
sb.append(".000Z");
return sb.toString();
}
/**
* Note that messages in the deleted database preserve the message's unique id; therefore, we
* can utilize this id to find references to the message. The only reference situation at this
* point is in the Body table; it is when sending messages via SmartForward and SmartReply
*/
// private boolean messageReferenced(ContentResolver cr, long id) {
// mBindArgument[0] = Long.toString(id);
// // See if this id is referenced in a body
// Cursor c = cr.query(Body.CONTENT_URI, Body.ID_PROJECTION, WHERE_BODY_SOURCE_MESSAGE_KEY,
// mBindArgument, null);
// try {
// return c.moveToFirst();
// } finally {
// c.close();
// }
// }
/*private*/ /**
* Serialize commands to delete items from the server; as we find items to delete, add their
* id's to the deletedId's array
*
* @param s the Serializer we're using to create post data
* @param deletedIds ids whose deletions are being sent to the server
* @param first whether or not this is the first command being sent
* @return true if SYNC_COMMANDS hasn't been sent (false otherwise)
* @throws IOException
*/
boolean sendDeletedItems(Serializer s, ArrayList<Long> deletedIds, boolean first)
throws IOException {
// ContentResolver cr = mContext.getContentResolver();
//
// // Find any of our deleted items
// Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION,
// MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
// // We keep track of the list of deleted item id's so that we can remove them from the
// // deleted table after the server receives our command
// deletedIds.clear();
// try {
// while (c.moveToNext()) {
// String serverId = c.getString(Message.LIST_SERVER_ID_COLUMN);
// // Keep going if there's no serverId
// if (serverId == null) {
// continue;
// // Also check if this message is referenced elsewhere
// } else if (messageReferenced(cr, c.getLong(Message.CONTENT_ID_COLUMN))) {
// userLog("Postponing deletion of referenced message: ", serverId);
// continue;
// } else if (first) {
// s.start(Tags.SYNC_COMMANDS);
// first = false;
// }
// // Send the command to delete this message
// s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
// deletedIds.add(c.getLong(Message.LIST_ID_COLUMN));
// }
// } finally {
// c.close();
// }
return first;
}
@Override
public boolean sendLocalChanges(Serializer s) throws IOException {
// ContentResolver cr = mContext.getContentResolver();
//
// // Never upsync from these folders
// if (mMailbox.mType == Mailbox.TYPE_DRAFTS || mMailbox.mType == Mailbox.TYPE_OUTBOX) {
// return false;
// }
//
// // This code is split out for unit testing purposes
// boolean firstCommand = sendDeletedItems(s, mDeletedIdList, true);
//
// // Find our trash mailbox, since deletions will have been moved there...
// long trashMailboxId =
// Mailbox.findMailboxOfType(mContext, mMailbox.mAccountKey, Mailbox.TYPE_TRASH);
//
// // Do the same now for updated items
// Cursor c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION,
// MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
//
// // We keep track of the list of updated item id's as we did above with deleted items
// mUpdatedIdList.clear();
// try {
// while (c.moveToNext()) {
// long id = c.getLong(Message.LIST_ID_COLUMN);
// // Say we've handled this update
// mUpdatedIdList.add(id);
// // We have the id of the changed item. But first, we have to find out its current
// // state, since the updated table saves the opriginal state
// Cursor currentCursor = cr.query(ContentUris.withAppendedId(Message.CONTENT_URI, id),
// UPDATES_PROJECTION, null, null, null);
// try {
// // If this item no longer exists (shouldn't be possible), just move along
// if (!currentCursor.moveToFirst()) {
// continue;
// }
// // Keep going if there's no serverId
// String serverId = currentCursor.getString(UPDATES_SERVER_ID_COLUMN);
// if (serverId == null) {
// continue;
// }
// // If the message is now in the trash folder, it has been deleted by the user
// if (currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN) == trashMailboxId) {
// if (firstCommand) {
// s.start(Tags.SYNC_COMMANDS);
// firstCommand = false;
// }
// // Send the command to delete this message
// s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
// continue;
// }
//
// boolean flagChange = false;
// boolean readChange = false;
//
// int flag = 0;
//
// // We can only send flag changes to the server in 12.0 or later
// if (mService.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
// flag = currentCursor.getInt(UPDATES_FLAG_COLUMN);
// if (flag != c.getInt(Message.LIST_FAVORITE_COLUMN)) {
// flagChange = true;
// }
// }
//
// int read = currentCursor.getInt(UPDATES_READ_COLUMN);
// if (read != c.getInt(Message.LIST_READ_COLUMN)) {
// readChange = true;
// }
//
// if (!flagChange && !readChange) {
// // In this case, we've got nothing to send to the server
// continue;
// }
//
// if (firstCommand) {
// s.start(Tags.SYNC_COMMANDS);
// firstCommand = false;
// }
// // Send the change to "read" and "favorite" (flagged)
// s.start(Tags.SYNC_CHANGE)
// .data(Tags.SYNC_SERVER_ID, c.getString(Message.LIST_SERVER_ID_COLUMN))
// .start(Tags.SYNC_APPLICATION_DATA);
// if (readChange) {
// s.data(Tags.EMAIL_READ, Integer.toString(read));
// }
// // "Flag" is a relatively complex concept in EAS 12.0 and above. It is not only
// // the boolean "favorite" that we think of in Gmail, but it also represents a
// // follow up action, which can include a subject, start and due dates, and even
// // recurrences. We don't support any of this as yet, but EAS 12.0 and higher
// // require that a flag contain a status, a type, and four date fields, two each
// // for start date and end (due) date.
// if (flagChange) {
// if (flag != 0) {
// // Status 2 = set flag
// s.start(Tags.EMAIL_FLAG).data(Tags.EMAIL_FLAG_STATUS, "2");
// // "FollowUp" is the standard type
// s.data(Tags.EMAIL_FLAG_TYPE, "FollowUp");
// long now = System.currentTimeMillis();
// Calendar calendar =
// GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
// calendar.setTimeInMillis(now);
// // Flags are required to have a start date and end date (duplicated)
// // First, we'll set the current date/time in GMT as the start time
// String utc = formatDateTime(calendar);
// s.data(Tags.TASK_START_DATE, utc).data(Tags.TASK_UTC_START_DATE, utc);
// // And then we'll use one week from today for completion date
// calendar.setTimeInMillis(now + 1*WEEKS);
// utc = formatDateTime(calendar);
// s.data(Tags.TASK_DUE_DATE, utc).data(Tags.TASK_UTC_DUE_DATE, utc);
// s.end();
// } else {
// s.tag(Tags.EMAIL_FLAG);
// }
// }
// s.end().end(); // SYNC_APPLICATION_DATA, SYNC_CHANGE
// } finally {
// currentCursor.close();
// }
// }
// } finally {
// c.close();
// }
//
// if (!firstCommand) {
// s.end(); // SYNC_COMMANDS
// }
return false;
}
public List<EasMessage> getMessages() {
return newEmails;
}
}

View File

@ -21,7 +21,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import android.content.ContentProviderOperation;
@ -38,7 +37,7 @@ import com.fsck.k9.mail.store.exchange.Eas;
* Handles the addition, deletion, and changes to folders in the user's Exchange account.
**/
public class FolderSyncParser extends AbstractSyncParser {
public class FolderSyncParser extends Parser {
public static final String TAG = "FolderSyncParser";
@ -57,7 +56,7 @@ public class FolderSyncParser extends AbstractSyncParser {
public static final int USER_MAILBOX_TYPE = 12;
public static final List<Integer> mValidFolderTypes = Arrays.asList(INBOX_TYPE, DRAFTS_TYPE,
DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE, CONTACTS_TYPE);
DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE);//, CALENDAR_TYPE, CONTACTS_TYPE);
// public static final String ALL_BUT_ACCOUNT_MAILBOX = MailboxColumns.ACCOUNT_KEY + "=? and " +
// MailboxColumns.TYPE + "!=" + Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
@ -81,8 +80,9 @@ public class FolderSyncParser extends AbstractSyncParser {
private EasStore easStore;
public FolderSyncParser(InputStream in, AbstractSyncAdapter adapter, EasStore easStore, List<Folder> folderList) throws IOException {
super(in, adapter, adapter.mFolder, adapter.mAccount);
public FolderSyncParser(InputStream in, EasStore easStore, List<Folder> folderList) throws IOException {
super(in);
this.easStore = easStore;
this.folderList = folderList;
// mAccountId = mAccount.mId;
@ -118,8 +118,7 @@ public class FolderSyncParser extends AbstractSyncParser {
}
}
} else if (tag == Tags.FOLDER_SYNC_KEY) {
getValue();
// mAccount.mSyncKey = getValue();
easStore.setStoreSyncKey(getValue());
userLog("New Account SyncKey: ", easStore.getStoreSyncKey());
} else if (tag == Tags.FOLDER_CHANGES) {
changesParser();
@ -367,27 +366,12 @@ public class FolderSyncParser extends AbstractSyncParser {
// }
}
/**
* Not needed for FolderSync parsing; everything is done within changesParser
*/
@Override
public void commandsParser() throws IOException {
void userLog(String ...strings) {
Log.i(K9.LOG_TAG, Arrays.toString(strings));
}
/**
* We don't need to implement commit() because all operations take place atomically within
* changesParser
*/
@Override
public void commit() throws IOException {
void userLog(String string, int num, String string2) {
Log.i(K9.LOG_TAG, string + num + string2);
}
@Override
public void wipe() {
}
@Override
public void responsesParser() throws IOException {
}
}

View File

@ -1,67 +0,0 @@
package com.fsck.k9.mail.store.exchange.adapter;
import java.io.IOException;
import java.io.InputStream;
public class GetItemEstimateParser extends Parser {
private String mCollectionId;
private int mEstimate;
public GetItemEstimateParser(InputStream in) throws IOException {
super(in);
}
@Override
public boolean parse() throws IOException {
boolean res = true;
if (nextTag(START_DOCUMENT) != Tags.GIE_GET_ITEM_ESTIMATE) {
throw new IOException();
}
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
switch (tag) {
case Tags.GIE_RESPONSE:
res = parseGIEResponse() && res;
break;
default:
skipTag();
}
}
return res;
}
private boolean parseGIEResponse() throws IOException {
if (nextTag(Tags.GIE_RESPONSE) == Tags.GIE_STATUS) {
int status = getValueInt();
if (status != 1)
return false;
if (nextTag(Tags.GIE_RESPONSE) == Tags.GIE_COLLECTION) {
String collectionId;
int estimate;
if (nextTag(Tags.GIE_COLLECTION) == Tags.GIE_COLLECTION_ID) {
collectionId = getValue();
} else return false;
if (nextTag(Tags.GIE_COLLECTION) == Tags.GIE_ESTIMATE) {
estimate = getValueInt();
} else return false;
mCollectionId = collectionId;
mEstimate = estimate;
return true;
}
}
return false;
}
public String getCollectionId() {
return mCollectionId;
}
public int getEstimate() {
return mEstimate;
}
}