Home | History | Annotate | Download | only in eas
      1 package com.android.exchange.eas;
      2 
      3 import android.content.ContentResolver;
      4 import android.content.ContentUris;
      5 import android.content.ContentValues;
      6 import android.content.Context;
      7 
      8 import com.android.emailcommon.provider.Account;
      9 import com.android.emailcommon.provider.EmailContent;
     10 import com.android.emailcommon.provider.MessageMove;
     11 import com.android.exchange.EasResponse;
     12 import com.android.exchange.adapter.MoveItemsParser;
     13 import com.android.exchange.adapter.Serializer;
     14 import com.android.exchange.adapter.Tags;
     15 import com.android.mail.utils.LogUtils;
     16 
     17 import org.apache.http.HttpEntity;
     18 
     19 import java.io.IOException;
     20 import java.util.List;
     21 
     22 /**
     23  * Performs a MoveItems request, which is used to move items between collections.
     24  * See http://msdn.microsoft.com/en-us/library/ee160102(v=exchg.80).aspx for more details.
     25  * TODO: Investigate how this interacts with ItemOperations.
     26  */
     27 public class EasMoveItems extends EasOperation {
     28 
     29     /** Result code indicating that no moved messages were found for this account. */
     30     public final static int RESULT_NO_MESSAGES = 0;
     31     public final static int RESULT_OK = 1;
     32     public final static int RESULT_EMPTY_RESPONSE = 2;
     33 
     34     private static class MoveResponse {
     35         public final String sourceMessageId;
     36         public final String newMessageId;
     37         public final int moveStatus;
     38 
     39         public MoveResponse(final String srcMsgId, final String dstMsgId, final int status) {
     40             sourceMessageId = srcMsgId;
     41             newMessageId = dstMsgId;
     42             moveStatus = status;
     43         }
     44     }
     45 
     46     private MessageMove mMove;
     47     private MoveResponse mResponse;
     48 
     49     public EasMoveItems(final Context context, final Account account) {
     50         super(context, account);
     51     }
     52 
     53     // TODO: Allow multiple messages in one request. Requires parser changes.
     54     public int upsyncMovedMessages() {
     55         final List<MessageMove> moves = MessageMove.getMoves(mContext, getAccountId());
     56         if (moves == null) {
     57             return RESULT_NO_MESSAGES;
     58         }
     59 
     60         final long[][] messageIds = new long[3][moves.size()];
     61         final int[] counts = new int[3];
     62         int result = RESULT_NO_MESSAGES;
     63 
     64         for (final MessageMove move : moves) {
     65             mMove = move;
     66             if (result >= 0) {
     67                 // If our previous time through the loop succeeded, keep making server requests.
     68                 // Otherwise, we carry through the loop for all messages with the last error
     69                 // response, which will stop trying this iteration and force the rest of the
     70                 // messages into the retry state.
     71                 result = performOperation();
     72             }
     73             final int status;
     74             if (result >= 0) {
     75                 if (result == RESULT_OK) {
     76                     processResponse(mMove, mResponse);
     77                     status = mResponse.moveStatus;
     78                 } else {
     79                     // TODO: Should this really be a retry?
     80                     // We got a 200 response with an empty payload. It's not clear we ought to
     81                     // retry, but this is how our implementation has worked in the past.
     82                     status = MoveItemsParser.STATUS_CODE_RETRY;
     83                 }
     84             } else {
     85                 // performOperation returned a negative status code, indicating a failure before the
     86                 // server actually was able to tell us yea or nay, so we must retry.
     87                 status = MoveItemsParser.STATUS_CODE_RETRY;
     88             }
     89             final int index;
     90             if (status <= 0) {
     91                 LogUtils.e(LOG_TAG, "MoveItems gave us an invalid status %d", status);
     92                 index = MoveItemsParser.STATUS_CODE_RETRY - 1;
     93             } else {
     94                 index = status - 1;
     95             }
     96             messageIds[index][counts[index]] = mMove.getMessageId();
     97             ++counts[index];
     98         }
     99 
    100         final ContentResolver cr = mContext.getContentResolver();
    101         MessageMove.upsyncSuccessful(cr, messageIds[0], counts[0]);
    102         MessageMove.upsyncFail(cr, messageIds[1], counts[1]);
    103         MessageMove.upsyncRetry(cr, messageIds[2], counts[2]);
    104 
    105         if (result >= 0) {
    106             return RESULT_OK;
    107         }
    108         return result;
    109     }
    110 
    111     @Override
    112     protected String getCommand() {
    113         return "MoveItems";
    114     }
    115 
    116     @Override
    117     protected HttpEntity getRequestEntity() throws IOException {
    118         final Serializer s = new Serializer();
    119         s.start(Tags.MOVE_MOVE_ITEMS);
    120         s.start(Tags.MOVE_MOVE);
    121         s.data(Tags.MOVE_SRCMSGID, mMove.getServerId());
    122         s.data(Tags.MOVE_SRCFLDID, mMove.getSourceFolderId());
    123         s.data(Tags.MOVE_DSTFLDID, mMove.getDestFolderId());
    124         s.end();
    125         s.end().done();
    126         return makeEntity(s);
    127     }
    128 
    129     @Override
    130     protected int handleResponse(final EasResponse response) throws IOException {
    131         if (!response.isEmpty()) {
    132             final MoveItemsParser parser = new MoveItemsParser(response.getInputStream());
    133             parser.parse();
    134             final String sourceMessageId = parser.getSourceServerId();
    135             final String newMessageId = parser.getNewServerId();
    136             final int status = parser.getStatusCode();
    137             mResponse = new MoveResponse(sourceMessageId, newMessageId, status);
    138             return RESULT_OK;
    139         }
    140         return RESULT_EMPTY_RESPONSE;
    141     }
    142 
    143     private void processResponse(final MessageMove request, final MoveResponse response) {
    144         // TODO: Eventually this should use a transaction.
    145         // TODO: Improve how the parser reports statuses and how we handle them here.
    146 
    147         final String sourceMessageId;
    148 
    149         if (response.sourceMessageId == null) {
    150             // The response didn't contain SrcMsgId, despite it being required.
    151             LogUtils.e(LOG_TAG,
    152                     "MoveItems response for message %d has no SrcMsgId, using request's server id",
    153                     request.getMessageId());
    154             sourceMessageId = request.getServerId();
    155         } else {
    156             sourceMessageId = response.sourceMessageId;
    157             if (!sourceMessageId.equals(request.getServerId())) {
    158                 // TODO: This is bad, but we still need to process the response. Just log for now.
    159                 LogUtils.e(LOG_TAG,
    160                         "MoveItems response for message %d has SrcMsgId != request's server id",
    161                         request.getMessageId());
    162             }
    163         }
    164 
    165         final ContentValues cv = new ContentValues(1);
    166         if (response.moveStatus == MoveItemsParser.STATUS_CODE_REVERT) {
    167             // Restore the old mailbox id
    168             cv.put(EmailContent.MessageColumns.MAILBOX_KEY, request.getSourceFolderKey());
    169         } else if (response.moveStatus == MoveItemsParser.STATUS_CODE_SUCCESS) {
    170             if (response.newMessageId != null && !response.newMessageId.equals(sourceMessageId)) {
    171                 cv.put(EmailContent.SyncColumns.SERVER_ID, response.newMessageId);
    172             }
    173         }
    174         if (cv.size() != 0) {
    175             mContext.getContentResolver().update(
    176                     ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI,
    177                             request.getMessageId()), cv, null, null);
    178         }
    179     }
    180 }
    181