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

Fix initial provision after K-9 is restarted.

This commit is contained in:
wongk 2011-12-10 18:50:44 -05:00
parent 4d032a861e
commit 66642d7ae1

View File

@ -83,28 +83,31 @@ public class EasStore extends Store {
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0]; private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
// This key is sent the first time we sync the folder hierarchy, and also the first time
// we sync the items any "collection" (emails in a folder).
private static final String INITIAL_SYNC_KEY = "0"; private static final String INITIAL_SYNC_KEY = "0";
static private final String PING_COMMAND = "Ping"; static private final String PING_COMMAND = "Ping";
static private final String PROVISION_COMMAND = "Provision";
// Command timeout is the the time allowed for reading data from an open connection before an // Command timeout is the the time allowed for reading data from an open connection before an
// IOException is thrown. After a small added allowance, our watch dog alarm goes off (allowing // IOException is thrown. After a small added allowance, our watch dog alarm goes off (allowing
// us to detect a silently dropped connection). The allowance is defined below. // us to detect a silently dropped connection). The allowance is defined below.
static private final int COMMAND_TIMEOUT = 30 * 1000; static private final int COMMAND_TIMEOUT = 30 * 1000;
// Connection timeout is the time given to connect to the server before reporting an IOException // Connection timeout is the time given to connect to the server before reporting an IOException.
static private final int CONNECTION_TIMEOUT = 20 * 1000; static private final int CONNECTION_TIMEOUT = 20 * 1000;
// This needs to be long enough to send the longest reasonable message, without being so long // 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 // as to effectively "hang" sending of mail. The standard 30 second timeout isn't long enough
// for pictures and the like. For now, we'll use 15 minutes, in the knowledge that any socket // for pictures and the like. For now, we'll use 15 minutes, in the knowledge that any socket
// failure would probably generate an Exception before timing out anyway // failure would probably generate an Exception before timing out anyway.
public static final int SEND_MAIL_TIMEOUT = 15 * 60 * 1000; public static final int SEND_MAIL_TIMEOUT = 15 * 60 * 1000;
// MSFT's custom HTTP result code indicating the need to provision // MSFT's custom HTTP result code indicating the need to provision.
static private final int HTTP_NEED_PROVISIONING = 449; static private final int HTTP_NEED_PROVISIONING = 449;
// The EAS protocol Provision status for "we implement all of the policies" // The EAS protocol Provision status for "we implement all of the policies".
static private final String PROVISION_STATUS_OK = "1"; static private final String PROVISION_STATUS_OK = "1";
// The EAS protocol Provision status meaning "we partially implement the policies" // The EAS protocol Provision status meaning "we partially implement the policies".
static private final String PROVISION_STATUS_PARTIAL = "2"; static private final String PROVISION_STATUS_PARTIAL = "2";
static public final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML"; static public final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML";
@ -115,7 +118,9 @@ public class EasStore extends Store {
private static final int MAX_DELAY_TIME = 5 * 60 * 1000; // 5 minutes private static final int MAX_DELAY_TIME = 5 * 60 * 1000; // 5 minutes
private static final int NORMAL_DELAY_TIME = 5000; private static final int NORMAL_DELAY_TIME = 5000;
// Reasonable default // The number of emails to fetch for each request to the server.
private static final int EMAIL_WINDOW_SIZE = 10;
public String mProtocolVersion; public String mProtocolVersion;
public Double mProtocolVersionDouble; public Double mProtocolVersionDouble;
protected String mDeviceId = null; protected String mDeviceId = null;
@ -138,8 +143,8 @@ public class EasStore extends Store {
* eas://user:password@server:port CONNECTION_SECURITY_NONE * eas://user:password@server:port CONNECTION_SECURITY_NONE
* eas+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL * eas+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
* eas+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED * eas+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
* eas+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
* eas+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL * eas+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
* eas+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
*/ */
public EasStore(Account account) throws MessagingException { public EasStore(Account account) throws MessagingException {
super(account); super(account);
@ -312,7 +317,7 @@ public class EasStore extends Store {
} }
private String getPolicyType() { private String getPolicyType() {
return (mProtocolVersionDouble >= return (getProtocolVersion() >=
Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE;
} }
@ -328,7 +333,7 @@ public class EasStore extends Store {
s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES); s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, getPolicyType()) s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, getPolicyType())
.end().end().end().done(); .end().end().end().done();
HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray()); HttpResponse resp = sendHttpClientPost(PROVISION_COMMAND, s.toByteArray());
try { try {
int code = resp.getStatusLine().getStatusCode(); int code = resp.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_OK) { if (code == HttpStatus.SC_OK) {
@ -381,7 +386,7 @@ public class EasStore extends Store {
s.end(); s.end();
} }
s.end().done(); // PROVISION_PROVISION s.end().done(); // PROVISION_PROVISION
HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray()); HttpResponse resp = sendHttpClientPost(PROVISION_COMMAND, s.toByteArray());
try { try {
int code = resp.getStatusLine().getStatusCode(); int code = resp.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_OK) { if (code == HttpStatus.SC_OK) {
@ -443,60 +448,64 @@ public class EasStore extends Store {
} }
} }
private Double getProtocolVersion() {
synchronized (mProtocolVersionDouble) {
if (mProtocolVersionDouble == null) {
try {
init();
}
catch (IOException e) {
e.printStackTrace();
}
catch (MessagingException e) {
e.printStackTrace();
}
}
return mProtocolVersionDouble;
}
}
protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException, MessagingException { protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException, MessagingException {
return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT); return sendHttpClientPost(cmd, new ByteArrayEntity(bytes), COMMAND_TIMEOUT);
} }
protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity) throws IOException, MessagingException {
return sendHttpClientPost(cmd, entity, COMMAND_TIMEOUT);
}
/** /**
* Convenience method for executePostWithTimeout for use other than with the Ping command * Handle executing an HTTP POST command with proper timeout and provisioning
*/
protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout)
throws IOException {
return executePostWithTimeout(client, method, timeout, false);
}
/**
* Handle executing an HTTP POST command with proper timeout, watchdog, and ping behavior
* @param client the HttpClient * @param client the HttpClient
* @param method the HttpPost * @param method the HttpPost
* @param timeout the timeout before failure, in ms * @param timeout the timeout before failure, in ms
* @param isPingCommand whether the POST is for the Ping command (requires wakelock logic) * @param noProvision prevents a provision request from being sent if determined to be necessary
* @return the HttpResponse * @return the HttpResponse
* @throws IOException * @throws IOException
*/ */
protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout, protected HttpResponse executePostWithTimeout(HttpClient client, HttpPost method, int timeout, boolean noProvision)
boolean isPingCommand) throws IOException { throws IOException {
HttpConnectionParams.setSoTimeout(method.getParams(), timeout); HttpConnectionParams.setSoTimeout(method.getParams(), timeout);
// synchronized(getSynchronizer()) { HttpResponse response = client.execute(method);
// mPendingPost = method; // If the request resulted in a provision error from the Exchange server, try provisioning.
// long alarmTime = timeout + WATCHDOG_TIMEOUT_ALLOWANCE; if (isProvisionError(response.getStatusLine().getStatusCode())
// if (isPingCommand) { && !noProvision) {
// SyncManager.runAsleep(mMailboxId, alarmTime); // Make sure to return the connection to the pool first.
// } else { reclaimConnection(response);
// SyncManager.setWatchdogAlarm(mMailboxId, alarmTime); try {
// } if (tryProvision()) {
// } // We provisioned successfully, re-send the request.
// try { response = client.execute(method);
return client.execute(method); }
// } finally { }
// synchronized(getSynchronizer()) { catch (MessagingException e) {
// if (isPingCommand) { e.printStackTrace();
// SyncManager.runAwake(mMailboxId); }
// } else { }
// SyncManager.clearWatchdogAlarm(mMailboxId); return response;
// }
// mPendingPost = null;
// }
// }
} }
protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout) protected HttpResponse sendHttpClientPost(String cmd, HttpEntity entity, int timeout)
throws IOException, MessagingException { throws IOException, MessagingException {
boolean isPingCommand = cmd.equals(PING_COMMAND); init();
boolean isPingCmd = cmd.equals(PING_COMMAND);
boolean isProvisionCmd = isPingCmd ? false : cmd.equals(PROVISION_COMMAND);
// Split the mail sending commands // Split the mail sending commands
String extra = null; String extra = null;
@ -519,10 +528,10 @@ public class EasStore extends Store {
} else if (entity != null) { } else if (entity != null) {
method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml"); method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
} }
setHeaders(method, !cmd.equals(PING_COMMAND)); setHeaders(method, !isPingCmd);
method.setEntity(entity); method.setEntity(entity);
return executePostWithTimeout(mHttpClient, method, timeout, isPingCommand); return executePostWithTimeout(mHttpClient, method, timeout, isProvisionCmd);
} }
protected HttpResponse sendHttpClientOptions() throws IOException, MessagingException { protected HttpResponse sendHttpClientOptions() throws IOException, MessagingException {
@ -540,7 +549,7 @@ public class EasStore extends Store {
* @param method the method we are going to send * @param method the method we are going to send
* @param usePolicyKey whether or not a policy key should be sent in the headers * @param usePolicyKey whether or not a policy key should be sent in the headers
*/ */
/*package*/ void setHeaders(HttpRequestBase method, boolean usePolicyKey) { private void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
method.setHeader("Authorization", mAuthString); method.setHeader("Authorization", mAuthString);
method.setHeader("MS-ASProtocolVersion", mProtocolVersion); method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
method.setHeader("Connection", "keep-alive"); method.setHeader("Connection", "keep-alive");
@ -643,11 +652,11 @@ public class EasStore extends Store {
LinkedList<Folder> folderList = new LinkedList<Folder>(); LinkedList<Folder> folderList = new LinkedList<Folder>();
try { try {
init();
Serializer s = new Serializer(); Serializer s = new Serializer();
s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY) s.start(Tags.FOLDER_FOLDER_SYNC)
.text(getStoreSyncKey()).end().end().done(); .start(Tags.FOLDER_SYNC_KEY)
.text(getStoreSyncKey())
.end().end().done();
HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray()); HttpResponse resp = sendHttpClientPost("FolderSync", s.toByteArray());
try { try {
int code = resp.getStatusLine().getStatusCode(); int code = resp.getStatusLine().getStatusCode();
@ -1023,29 +1032,25 @@ public class EasStore extends Store {
public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
try { try {
EasEmailSyncParser syncParser = getMessagesInternal(null, null, null, start, end); List<EasMessage> messages = getMessagesInternal(null, null, null, start, end);
List<EasMessage> messages = syncParser.getMessages();
return messages.toArray(EMPTY_MESSAGE_ARRAY); return messages.toArray(EMPTY_MESSAGE_ARRAY);
} catch (IOException e) { } catch (IOException e) {
throw new MessagingException("getMessages call failed", e); throw new MessagingException("getMessages call failed", e);
} }
} }
private EasEmailSyncParser getMessagesInternal(Message[] messages, FetchProfile fp, MessageRetrievalListener listener, private List<EasMessage> getMessagesInternal(Message[] messages, FetchProfile fp, MessageRetrievalListener listener,
int start, int end) throws IOException, MessagingException { int start, int end) throws IOException, MessagingException {
List<EasMessage> easMessages = new ArrayList<EasMessage>();
Boolean moreAvailable = true;
while (moreAvailable && easMessages.isEmpty()) {
Serializer s = new Serializer(); Serializer s = new Serializer();
EasEmailSyncParser syncParser = null;
// EmailSyncAdapter target = new EmailSyncAdapter(this, mAccount);
String className = "Email";
String syncKey = getSyncKey(); String syncKey = getSyncKey();
// userLog("sync, sending ", className, " syncKey: ", syncKey);
s.start(Tags.SYNC_SYNC) s.start(Tags.SYNC_SYNC)
.start(Tags.SYNC_COLLECTIONS) .start(Tags.SYNC_COLLECTIONS)
.start(Tags.SYNC_COLLECTION) .start(Tags.SYNC_COLLECTION)
.data(Tags.SYNC_CLASS, className) .data(Tags.SYNC_CLASS, "Email")
.data(Tags.SYNC_SYNC_KEY, syncKey) .data(Tags.SYNC_SYNC_KEY, syncKey)
.data(Tags.SYNC_COLLECTION_ID, mServerId); .data(Tags.SYNC_COLLECTION_ID, mServerId);
@ -1062,22 +1067,26 @@ public class EasStore extends Store {
s.tag(Tags.SYNC_DELETES_AS_MOVES); s.tag(Tags.SYNC_DELETES_AS_MOVES);
// If messages is null, we only want to sync changes.
if (messages == null) { if (messages == null) {
s.tag(Tags.SYNC_GET_CHANGES); s.tag(Tags.SYNC_GET_CHANGES);
// EASTODO: s.data(Tags.SYNC_WINDOW_SIZE, Integer.toString(end - start + 1));
} }
// Only fetch 10 messages at a time.
s.data(Tags.SYNC_WINDOW_SIZE, Integer.toString(EMAIL_WINDOW_SIZE));
// Handle options // Handle options
s.start(Tags.SYNC_OPTIONS); s.start(Tags.SYNC_OPTIONS);
if (messages == null) { if (messages == null) {
// Set the lookback appropriately (EAS calls this a "filter") for all but Contacts // Set the time frame appropriately (EAS calls this a "filter") for all but Contacts.
s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter()); s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter());
} }
// Enable MimeSupport // Enable MimeSupport
s.data(Tags.SYNC_MIME_SUPPORT, "2"); s.data(Tags.SYNC_MIME_SUPPORT, "2");
// Set the truncation amount for all classes // Set the truncation amount for all classes.
if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) { if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
s.start(Tags.BASE_BODY_PREFERENCE) s.start(Tags.BASE_BODY_PREFERENCE)
// HTML for email; plain text for everything else // HTML for email; plain text for everything else.
.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_MIME); .data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_MIME);
if (!fetchBody) { if (!fetchBody) {
@ -1100,16 +1109,15 @@ public class EasStore extends Store {
} }
s.end(); s.end();
} else { } else {
// Use enormous timeout for initial sync, which empirically can take a while longer // Use enormous timeout for initial sync, which empirically can take a while longer.
timeout = 120 * 1000; timeout = 120 * 1000;
} }
if (messages != null) { if (messages != null) {
s.start(Tags.SYNC_COMMANDS); s.start(Tags.SYNC_COMMANDS);
for (Message msg : messages) { for (Message msg : messages) {
String serverId = msg.getUid();
s.start(Tags.SYNC_FETCH); s.start(Tags.SYNC_FETCH);
s.data(Tags.SYNC_SERVER_ID, serverId); s.data(Tags.SYNC_SERVER_ID, msg.getUid());
s.end(); s.end();
} }
s.end(); s.end();
@ -1123,14 +1131,9 @@ public class EasStore extends Store {
if (code == HttpStatus.SC_OK) { if (code == HttpStatus.SC_OK) {
InputStream is = resp.getEntity().getContent(); InputStream is = resp.getEntity().getContent();
if (is != null) { if (is != null) {
syncParser = new EasEmailSyncParser(is, this, mAccount); EasEmailSyncParser parser = new EasEmailSyncParser(is, this, mAccount);
boolean moreAvailable = syncParser.parse(); moreAvailable = parser.parse();
if (moreAvailable && !syncParser.hasMessages()) { easMessages.addAll(parser.getMessages());
// Make sure we free the connection to the pool before recursing. Otherwise
// we'll dead lock.
reclaimConnection(resp);
return getMessagesInternal(messages, fp, listener, start, end);
}
} else { } else {
Log.d(K9.LOG_TAG, "Empty input stream in sync command response"); Log.d(K9.LOG_TAG, "Empty input stream in sync command response");
} }
@ -1146,7 +1149,8 @@ public class EasStore extends Store {
} finally { } finally {
reclaimConnection(resp); reclaimConnection(resp);
} }
return syncParser; }
return easMessages;
} }
private String getEmailFilter() { private String getEmailFilter() {
@ -1230,8 +1234,7 @@ public class EasStore extends Store {
boolean fetchBody = fp.contains(FetchProfile.Item.BODY); boolean fetchBody = fp.contains(FetchProfile.Item.BODY);
if (fetchBodySane || fetchBody) { if (fetchBodySane || fetchBody) {
try { try {
EasEmailSyncParser syncParser = getMessagesInternal(messages, fp, listener, -1, -1); messages = getMessagesInternal(messages, fp, listener, -1, -1).toArray(EMPTY_MESSAGE_ARRAY);
messages = syncParser.getMessages().toArray(EMPTY_MESSAGE_ARRAY);
} catch (IOException e) { } catch (IOException e) {
throw new MessagingException("IO exception while fetching messages", e); throw new MessagingException("IO exception while fetching messages", e);
} }