Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ecc10e57 authored by Daniel Applebaum's avatar Daniel Applebaum
Browse files

Fixes Issue 1224

Implement DEFLATE compression for IMAP communication, enabled by
default.  User can disable compression for Wi-Fi, Mobile, or Other
networks, if it causes problems or if uncompressed communication is
faster, which is possible on Wi-Fi and wired networks, especially.

"Other" is to allow for the Android platform to introduce new
networking types without having to immediately change K-9 Mail.
However, as those arise, new network types should be added as explicit
types in K-9 Mail.

parent c0e4220b
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -332,6 +332,11 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
    <string name="account_setup_incoming_delete_policy_delete_label">Delete from server</string>
    <string name="account_setup_incoming_delete_policy_markread_label">Mark as read on server</string>
    
    <string name="account_setup_incoming_compression_label">Use compression on network:</string>
    <string name="account_setup_incoming_mobile_label">Mobile</string>
    <string name="account_setup_incoming_wifi_label">Wi-Fi</string>
    <string name="account_setup_incoming_other_label">Other</string>
    
    <string name="account_setup_expunge_policy_label">Expunge messages</string>
    <string name="account_setup_expunge_policy_immediately">Immediately after delete or move</string>
    <string name="account_setup_expunge_policy_on_poll">During each poll</string>
+63 −1
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ package com.fsck.k9;

import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.Uri;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Folder;
@@ -12,8 +13,11 @@ import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Account stores all of the settings for a single account defined by the user. It is able to save
@@ -30,6 +34,11 @@ public class Account
    public static final int DELETE_POLICY_ON_DELETE = 2;
    public static final int DELETE_POLICY_MARK_AS_READ = 3;
    
    public static final String TYPE_WIFI = "WIFI";
    public static final String TYPE_MOBILE = "MOBILE";
    public static final String TYPE_OTHER = "OTHER";
    private static String[] networkTypes = { TYPE_WIFI, TYPE_MOBILE, TYPE_OTHER };

    /**
     * <pre>
     * 0 - Never (DELETE_POLICY_NEVER)
@@ -69,6 +78,7 @@ public class Account
    private boolean mIsSignatureBeforeQuotedText;
    private String mExpungePolicy = EXPUNGE_IMMEDIATELY;
    private int mMaxPushFolders;
    private Map<String, Boolean> compressionMap = new ConcurrentHashMap<String, Boolean>(); 

    private List<Identity> identities;

@@ -158,6 +168,13 @@ public class Account

        mMaxPushFolders = preferences.getPreferences().getInt(mUuid + ".maxPushFolders", 10);
        
        for (String type : networkTypes)
        {
            Boolean useCompression = preferences.getPreferences().getBoolean(mUuid + ".useCompression." + type,
                    true);
            compressionMap.put(type, useCompression);
        }

        // Between r418 and r431 (version 0.103), folder names were set empty if the Incoming settings were
        // opened for non-IMAP accounts.  0.103 was never a market release, so perhaps this code
        // should be deleted sometime soon
@@ -292,6 +309,10 @@ public class Account
        editor.remove(mUuid + ".signatureBeforeQuotedText");
        editor.remove(mUuid + ".expungePolicy");
        editor.remove(mUuid + ".maxPushFolders");
        for (String type : networkTypes)
        {
            editor.remove(mUuid + ".useCompression." + type);
        }
        deleteIdentities(preferences.getPreferences(), editor);
        editor.commit();
    }
@@ -364,6 +385,14 @@ public class Account
        editor.putBoolean(mUuid + ".signatureBeforeQuotedText", this.mIsSignatureBeforeQuotedText);
        editor.putString(mUuid + ".expungePolicy", mExpungePolicy);
        editor.putInt(mUuid + ".maxPushFolders", mMaxPushFolders);
        for (String type : networkTypes)
        {
            Boolean useCompression = compressionMap.get(type);
            if (useCompression != null)
            {
                editor.putBoolean(mUuid + ".useCompression." + type, useCompression);
            }
        }
        saveIdentities(preferences.getPreferences(), editor);

        editor.commit();
@@ -810,6 +839,39 @@ public class Account
        return mDescription;
    }
    
    public void setCompression(String networkType, boolean useCompression)
    {
        compressionMap.put(networkType, useCompression);
    }
    
    public boolean useCompression(String networkType)
    {
        Boolean useCompression = compressionMap.get(networkType);
        if (useCompression == null)
        {
            return true;
        }
        else
        {
            return useCompression;
        }
    }
    
    public boolean useCompression(int type)
    {
        String networkType = TYPE_OTHER;
        switch (type)
        {
            case ConnectivityManager.TYPE_MOBILE:
                networkType = TYPE_MOBILE;
                break;
            case ConnectivityManager.TYPE_WIFI:
                networkType = TYPE_WIFI;
                break;
        }
        return useCompression(networkType);
    }
    
    @Override
    public boolean equals(Object o)
    {
+15 −1
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;

import com.fsck.k9.*;
import com.fsck.k9.activity.ChooseFolder;
import java.io.UnsupportedEncodingException;
@@ -79,6 +80,9 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
    private Button mNextButton;
    private Account mAccount;
    private boolean mMakeDefault;
    private CheckBox compressionMobile;
    private CheckBox compressionWifi;
    private CheckBox compressionOther;

    public static void actionIncomingSettings(Activity context, Account account, boolean makeDefault)
    {
@@ -118,6 +122,9 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
        mWebdavAuthPathView = (EditText)findViewById(R.id.webdav_auth_path);
        mWebdavMailboxPathView = (EditText)findViewById(R.id.webdav_mailbox_path);
        mNextButton = (Button)findViewById(R.id.next);
        compressionMobile = (CheckBox)findViewById(R.id.compression_mobile);
        compressionWifi = (CheckBox)findViewById(R.id.compression_wifi);
        compressionOther = (CheckBox)findViewById(R.id.compression_other);

        mImapFolderDrafts.setOnClickListener(this);
        mImapFolderSent.setOnClickListener(this);
@@ -279,6 +286,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
                findViewById(R.id.webdav_path_prefix_section).setVisibility(View.GONE);
                findViewById(R.id.webdav_path_debug_section).setVisibility(View.GONE);
                findViewById(R.id.account_auth_type).setVisibility(View.GONE);
                findViewById(R.id.compression_section).setVisibility(View.GONE);
                mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER);


@@ -312,6 +320,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
                /** Hide the unnecessary fields */
                findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE);
                findViewById(R.id.account_auth_type).setVisibility(View.GONE);
                findViewById(R.id.compression_section).setVisibility(View.GONE);
                if (uri.getPath() != null && uri.getPath().length() > 0)
                {
                    String[] pathParts = uri.getPath().split("\\|");
@@ -358,7 +367,9 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
                    SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
                }
            }

            compressionMobile.setChecked(mAccount.useCompression(Account.TYPE_MOBILE));
            compressionWifi.setChecked(mAccount.useCompression(Account.TYPE_WIFI));
            compressionOther.setChecked(mAccount.useCompression(Account.TYPE_OTHER));
            if (uri.getHost() != null)
            {
                mServerView.setText(uri.getHost());
@@ -518,6 +529,9 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
            mAccount.setSentFolderName(mImapFolderSent.getText().toString());
            mAccount.setTrashFolderName(mImapFolderTrash.getText().toString());
            mAccount.setOutboxFolderName(mImapFolderOutbox.getText().toString());
            mAccount.setCompression(Account.TYPE_MOBILE, compressionMobile.isChecked());
            mAccount.setCompression(Account.TYPE_WIFI, compressionWifi.isChecked());
            mAccount.setCompression(Account.TYPE_OTHER, compressionOther.isChecked());
            AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
        }
        catch (Exception e)
+61 −5
Original line number Diff line number Diff line

package com.fsck.k9.mail.store;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;

import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.PeekableInputStream;
@@ -13,6 +15,9 @@ import com.fsck.k9.mail.store.ImapResponseParser.ImapList;
import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse;
import com.fsck.k9.mail.transport.CountingOutputStream;
import com.fsck.k9.mail.transport.EOLConvertingOutputStream;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZInputStream;
import com.jcraft.jzlib.ZOutputStream;
import com.beetstra.jutf7.CharsetProvider;

import javax.net.ssl.SSLContext;
@@ -68,6 +73,9 @@ public class ImapStore extends Store
    private static final String CAPABILITY_CAPABILITY = "CAPABILITY";
    private static final String COMMAND_CAPABILITY = "CAPABILITY";
    
    private static final String CAPABILITY_COMPRESS_DEFLATE = "COMPRESS=DEFLATE";
    private static final String COMMAND_COMPRESS_DEFLATE = "COMPRESS DEFLATE";

    private String mHost;
    private int mPort;
    private String mUsername;
@@ -100,11 +108,12 @@ public class ImapStore extends Store
     * imap+tls+://auth:user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
     * imap+ssl+://auth:user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
     * imap+ssl://auth:user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
     *
     * @param _uri
     */
    public ImapStore(Account account) throws MessagingException
    {
        super(account);

        URI uri;
        try
        {
@@ -1910,6 +1919,7 @@ public class ImapStore extends Store
                
                if (hasCapability(CAPABILITY_CAPABILITY) == false)
                {
                    if (K9.DEBUG)
                        Log.i(K9.LOG_TAG, "Did not get capabilities in banner, requesting CAPABILITY for " + getLogId());
                    List<ImapResponse> responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY));
                    if (responses.size() != 2)
@@ -1954,9 +1964,10 @@ public class ImapStore extends Store
                    if (mAuthType == AuthType.CRAM_MD5)
                    {
                        authCramMD5();
                        // The authCramMD5 method on the previous line does not allow for handling updated capabilities
                        // The authCramMD5 method called on the previous line does not allow for handling updated capabilities
                        // sent by the server.  So, to make sure we update to the post-authentication capability list
                        // we fetch the capabilities here.
                        if (K9.DEBUG)
                            Log.i(K9.LOG_TAG, "Updating capabilities after CRAM-MD5 authentication for " + getLogId());
                        List<ImapResponse> responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY));
                        if (responses.size() != 2)
@@ -1980,6 +1991,51 @@ public class ImapStore extends Store
                {
                    throw new AuthenticationFailedException(null, me);
                }
                if (K9.DEBUG)
                {
                    Log.d(K9.LOG_TAG, CAPABILITY_COMPRESS_DEFLATE + " = " + hasCapability(CAPABILITY_COMPRESS_DEFLATE));
                }
                if (hasCapability(CAPABILITY_COMPRESS_DEFLATE))
                {
                    ConnectivityManager connectivityManager = (ConnectivityManager)K9.app.getSystemService(Context.CONNECTIVITY_SERVICE);
                    boolean useCompression = true;
                    
                    NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
                    if (netInfo != null)
                    {
                        int type = netInfo.getType();
                        if (K9.DEBUG)
                            Log.d(K9.LOG_TAG, "On network type " + type);
                        useCompression = mAccount.useCompression(type);
                        
                    }
                    if (K9.DEBUG)
                        Log.d(K9.LOG_TAG, "useCompression " + useCompression);
                    if (useCompression)
                    {
                        try
                        {
                            executeSimpleCommand(COMMAND_COMPRESS_DEFLATE);
                            ZInputStream zInputStream = new ZInputStream(mSocket.getInputStream(), true);
                            zInputStream.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
                            mIn = new PeekableInputStream(new BufferedInputStream(zInputStream, 1024));
                            mParser = new ImapResponseParser(mIn);
                            ZOutputStream zOutputStream = new ZOutputStream(mSocket.getOutputStream(), JZlib.Z_BEST_SPEED, true);
                            mOut = new BufferedOutputStream(zOutputStream, 1024);
                            zOutputStream.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
                            if (K9.DEBUG)
                            {
                                Log.i(K9.LOG_TAG, "Compression enabled for " + getLogId());
                            }
                        }
                        catch (Exception e)
                        {
                            Log.e(K9.LOG_TAG, "Unable to negotiate compression", e);
                        }
                    }
                }
                
                
                if (K9.DEBUG)
                    Log.d(K9.LOG_TAG, "NAMESPACE = " + hasCapability(CAPABILITY_NAMESPACE)
                          + ", mPathPrefix = " + mPathPrefix);
+94 −0
Original line number Diff line number Diff line
/* -*-mode:java; c-basic-offset:2; -*- */
/*
Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright 
     notice, this list of conditions and the following disclaimer in 
     the documentation and/or other materials provided with the distribution.

  3. The names of the authors may not be used to endorse or promote products
     derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * This program is based on zlib-1.1.3, so all credit should go authors
 * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
 * and contributors of zlib.
 */

package com.jcraft.jzlib;

final class Adler32{

  // largest prime smaller than 65536
  static final private int BASE=65521; 
  // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
  static final private int NMAX=5552;

  long adler32(long adler, byte[] buf, int index, int len){
    if(buf == null){ return 1L; }

    long s1=adler&0xffff;
    long s2=(adler>>16)&0xffff;
    int k;

    while(len > 0) {
      k=len<NMAX?len:NMAX;
      len-=k;
      while(k>=16){
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        s1+=buf[index++]&0xff; s2+=s1;
        k-=16;
      }
      if(k!=0){
        do{
          s1+=buf[index++]&0xff; s2+=s1;
        }
        while(--k!=0);
      }
      s1%=BASE;
      s2%=BASE;
    }
    return (s2<<16)|s1;
  }

  /*
  private java.util.zip.Adler32 adler=new java.util.zip.Adler32();
  long adler32(long value, byte[] buf, int index, int len){
    if(value==1) {adler.reset();}
    if(buf==null) {adler.reset();}
    else{adler.update(buf, index, len);}
    return adler.getValue();
  }
  */
}
Loading