mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-23 18:02:15 -05:00
bin directory should never have been checked in
This commit is contained in:
parent
ece618b2ee
commit
12c6e53141
@ -1,117 +0,0 @@
|
|||||||
/* ====================================================================
|
|
||||||
* Copyright (c) 2006 J.T. Beetstra
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.beetstra.jutf7;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Represent a base 64 mapping. The 64 characters used in the encoding can be
|
|
||||||
* specified, since modified-UTF-7 uses other characters than UTF-7 (',' instead
|
|
||||||
* of '/').
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* The exact type of the arguments and result values is adapted to the needs of
|
|
||||||
* the encoder and decoder, as opposed to following a strict interpretation of
|
|
||||||
* base 64.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* Base 64, as specified in RFC 2045, is an encoding used to encode bytes as
|
|
||||||
* characters. In (modified-)UTF-7 however, it is used to encode characters as
|
|
||||||
* bytes, using some intermediate steps:
|
|
||||||
* </p>
|
|
||||||
* <ol>
|
|
||||||
* <li>Encode all characters as a 16-bit (UTF-16) integer value</li>
|
|
||||||
* <li>Write this as stream of bytes (most-significant first)</li>
|
|
||||||
* <li>Encode these bytes using (modified) base 64 encoding</li>
|
|
||||||
* <li>Write the thus formed stream of characters as a stream of bytes, using
|
|
||||||
* ASCII encoding</li>
|
|
||||||
* </ol>
|
|
||||||
*
|
|
||||||
* @author Jaap Beetstra
|
|
||||||
*/
|
|
||||||
class Base64Util {
|
|
||||||
private static final int ALPHABET_LENGTH = 64;
|
|
||||||
private final char[] alphabet;
|
|
||||||
private final int[] inverseAlphabet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the class with the specified encoding/decoding alphabet.
|
|
||||||
*
|
|
||||||
* @param alphabet
|
|
||||||
* @throws IllegalArgumentException if alphabet is not 64 characters long or
|
|
||||||
* contains characters which are not 7-bit ASCII
|
|
||||||
*/
|
|
||||||
Base64Util(final String alphabet) {
|
|
||||||
this.alphabet = alphabet.toCharArray();
|
|
||||||
if (alphabet.length() != ALPHABET_LENGTH)
|
|
||||||
throw new IllegalArgumentException("alphabet has incorrect length (should be 64, not "
|
|
||||||
+ alphabet.length() + ")");
|
|
||||||
inverseAlphabet = new int[128];
|
|
||||||
Arrays.fill(inverseAlphabet, -1);
|
|
||||||
for (int i = 0; i < this.alphabet.length; i++) {
|
|
||||||
final char ch = this.alphabet[i];
|
|
||||||
if (ch >= 128)
|
|
||||||
throw new IllegalArgumentException("invalid character in alphabet: " + ch);
|
|
||||||
inverseAlphabet[ch] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the six bits represented by the specified
|
|
||||||
* character.
|
|
||||||
*
|
|
||||||
* @param ch The character, as a ASCII encoded byte
|
|
||||||
* @return The six bits, as an integer value, or -1 if the byte is not in
|
|
||||||
* the alphabet
|
|
||||||
*/
|
|
||||||
int getSextet(final byte ch) {
|
|
||||||
if (ch >= 128)
|
|
||||||
return -1;
|
|
||||||
return inverseAlphabet[ch];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells whether the alphabet contains the specified character.
|
|
||||||
*
|
|
||||||
* @param ch The character
|
|
||||||
* @return true if the alphabet contains <code>ch</code>, false otherwise
|
|
||||||
*/
|
|
||||||
boolean contains(final char ch) {
|
|
||||||
if (ch >= 128)
|
|
||||||
return false;
|
|
||||||
return inverseAlphabet[ch] >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the six bit group as a character.
|
|
||||||
*
|
|
||||||
* @param sextet The six bit group to be encoded
|
|
||||||
* @return The ASCII value of the character
|
|
||||||
*/
|
|
||||||
byte getChar(final int sextet) {
|
|
||||||
return (byte)alphabet[sextet];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
/* ====================================================================
|
|
||||||
* Copyright (c) 2006 J.T. Beetstra
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.beetstra.jutf7;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Charset service-provider class used for both variants of the UTF-7 charset
|
|
||||||
* and the modified-UTF-7 charset.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author Jaap Beetstra
|
|
||||||
*/
|
|
||||||
public class CharsetProvider extends java.nio.charset.spi.CharsetProvider {
|
|
||||||
private static final String UTF7_NAME = "UTF-7";
|
|
||||||
private static final String UTF7_O_NAME = "X-UTF-7-OPTIONAL";
|
|
||||||
private static final String UTF7_M_NAME = "X-MODIFIED-UTF-7";
|
|
||||||
private static final String[] UTF7_ALIASES = new String[] {
|
|
||||||
"UNICODE-1-1-UTF-7", "CSUNICODE11UTF7", "X-RFC2152", "X-RFC-2152"
|
|
||||||
};
|
|
||||||
private static final String[] UTF7_O_ALIASES = new String[] {
|
|
||||||
"X-RFC2152-OPTIONAL", "X-RFC-2152-OPTIONAL"
|
|
||||||
};
|
|
||||||
private static final String[] UTF7_M_ALIASES = new String[] {
|
|
||||||
"X-IMAP-MODIFIED-UTF-7", "X-IMAP4-MODIFIED-UTF7", "X-IMAP4-MODIFIED-UTF-7",
|
|
||||||
"X-RFC3501", "X-RFC-3501"
|
|
||||||
};
|
|
||||||
private Charset utf7charset = new UTF7Charset(UTF7_NAME, UTF7_ALIASES, false);
|
|
||||||
private Charset utf7oCharset = new UTF7Charset(UTF7_O_NAME, UTF7_O_ALIASES, true);
|
|
||||||
private Charset imap4charset = new ModifiedUTF7Charset(UTF7_M_NAME, UTF7_M_ALIASES);
|
|
||||||
private List charsets;
|
|
||||||
|
|
||||||
public CharsetProvider() {
|
|
||||||
charsets = Arrays.asList(new Object[] {
|
|
||||||
utf7charset, imap4charset, utf7oCharset
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public Charset charsetForName(String charsetName) {
|
|
||||||
charsetName = charsetName.toUpperCase();
|
|
||||||
for (Iterator iter = charsets.iterator(); iter.hasNext();) {
|
|
||||||
Charset charset = (Charset)iter.next();
|
|
||||||
if (charset.name().equals(charsetName))
|
|
||||||
return charset;
|
|
||||||
}
|
|
||||||
for (Iterator iter = charsets.iterator(); iter.hasNext();) {
|
|
||||||
Charset charset = (Charset)iter.next();
|
|
||||||
if (charset.aliases().contains(charsetName))
|
|
||||||
return charset;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public Iterator charsets() {
|
|
||||||
return charsets.iterator();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
/* ====================================================================
|
|
||||||
* Copyright (c) 2006 J.T. Beetstra
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.beetstra.jutf7;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* The character set specified in RFC 3501 to use for IMAP4rev1 mailbox name
|
|
||||||
* encoding.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see <a href="http://tools.ietf.org/html/rfc3501">RFC 3501< /a>
|
|
||||||
* @author Jaap Beetstra
|
|
||||||
*/
|
|
||||||
class ModifiedUTF7Charset extends UTF7StyleCharset {
|
|
||||||
private static final String MODIFIED_BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
+ "abcdefghijklmnopqrstuvwxyz" + "0123456789+,";
|
|
||||||
|
|
||||||
ModifiedUTF7Charset(String name, String[] aliases) {
|
|
||||||
super(name, aliases, MODIFIED_BASE64_ALPHABET, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean canEncodeDirectly(char ch) {
|
|
||||||
if (ch == shift())
|
|
||||||
return false;
|
|
||||||
return ch >= 0x20 && ch <= 0x7E;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte shift() {
|
|
||||||
return '&';
|
|
||||||
}
|
|
||||||
|
|
||||||
byte unshift() {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/* ====================================================================
|
|
||||||
* Copyright (c) 2006 J.T. Beetstra
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.beetstra.jutf7;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* The character set specified in RFC 2152. Two variants are supported using the
|
|
||||||
* encodeOptional constructor flag
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see <a href="http://tools.ietf.org/html/rfc2152">RFC 2152< /a>
|
|
||||||
* @author Jaap Beetstra
|
|
||||||
*/
|
|
||||||
class UTF7Charset extends UTF7StyleCharset {
|
|
||||||
private static final String BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
+ "abcdefghijklmnopqrstuvwxyz" + "0123456789+/";
|
|
||||||
private static final String SET_D = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'(),-./:?";
|
|
||||||
private static final String SET_O = "!\"#$%&*;<=>@[]^_`{|}";
|
|
||||||
private static final String RULE_3 = " \t\r\n";
|
|
||||||
final String directlyEncoded;
|
|
||||||
|
|
||||||
UTF7Charset(String name, String[] aliases, boolean includeOptional) {
|
|
||||||
super(name, aliases, BASE64_ALPHABET, false);
|
|
||||||
if (includeOptional)
|
|
||||||
this.directlyEncoded = SET_D + SET_O + RULE_3;
|
|
||||||
else
|
|
||||||
this.directlyEncoded = SET_D + RULE_3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see com.beetstra.jutf7.UTF7StyleCharset#canEncodeDirectly(char)
|
|
||||||
*/
|
|
||||||
boolean canEncodeDirectly(char ch) {
|
|
||||||
return directlyEncoded.indexOf(ch) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see com.beetstra.jutf7.UTF7StyleCharset#shift()
|
|
||||||
*/
|
|
||||||
byte shift() {
|
|
||||||
return '+';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see com.beetstra.jutf7.UTF7StyleCharset#unshift()
|
|
||||||
*/
|
|
||||||
byte unshift() {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
/* ====================================================================
|
|
||||||
* Copyright (c) 2006 J.T. Beetstra
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.beetstra.jutf7;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.CharsetDecoder;
|
|
||||||
import java.nio.charset.CharsetEncoder;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Abstract base class for UTF-7 style encoding and decoding.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author Jaap Beetstra
|
|
||||||
*/
|
|
||||||
abstract class UTF7StyleCharset extends Charset {
|
|
||||||
private static final List CONTAINED = Arrays.asList(new String[] {
|
|
||||||
"US-ASCII", "ISO-8859-1", "UTF-8", "UTF-16", "UTF-16LE", "UTF-16BE"
|
|
||||||
});
|
|
||||||
final boolean strict;
|
|
||||||
Base64Util base64;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Besides the name and aliases, two additional parameters are required.
|
|
||||||
* First the base 64 alphabet used; in modified UTF-7 a slightly different
|
|
||||||
* alphabet is used. Additionally, it should be specified if encoders and
|
|
||||||
* decoders should be strict about the interpretation of malformed encoded
|
|
||||||
* sequences. This is used since modified UTF-7 specifically disallows some
|
|
||||||
* constructs which are allowed (or not specifically disallowed) in UTF-7
|
|
||||||
* (RFC 2152).
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param canonicalName The name as defined in java.nio.charset.Charset
|
|
||||||
* @param aliases The aliases as defined in java.nio.charset.Charset
|
|
||||||
* @param alphabet The base 64 alphabet used
|
|
||||||
* @param strict True if strict handling of sequences is requested
|
|
||||||
*/
|
|
||||||
protected UTF7StyleCharset(String canonicalName, String[] aliases, String alphabet,
|
|
||||||
boolean strict) {
|
|
||||||
super(canonicalName, aliases);
|
|
||||||
this.base64 = new Base64Util(alphabet);
|
|
||||||
this.strict = strict;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.nio.charset.Charset#contains(java.nio.charset.Charset)
|
|
||||||
*/
|
|
||||||
public boolean contains(final Charset cs) {
|
|
||||||
return CONTAINED.contains(cs.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.nio.charset.Charset#newDecoder()
|
|
||||||
*/
|
|
||||||
public CharsetDecoder newDecoder() {
|
|
||||||
return new UTF7StyleCharsetDecoder(this, base64, strict);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.nio.charset.Charset#newEncoder()
|
|
||||||
*/
|
|
||||||
public CharsetEncoder newEncoder() {
|
|
||||||
return new UTF7StyleCharsetEncoder(this, base64, strict);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells if a character can be encoded using simple (US-ASCII) encoding or
|
|
||||||
* requires base 64 encoding.
|
|
||||||
*
|
|
||||||
* @param ch The character
|
|
||||||
* @return True if the character can be encoded directly, false otherwise
|
|
||||||
*/
|
|
||||||
abstract boolean canEncodeDirectly(char ch);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns character used to switch to base 64 encoding.
|
|
||||||
*
|
|
||||||
* @return The shift character
|
|
||||||
*/
|
|
||||||
abstract byte shift();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns character used to switch from base 64 encoding to simple
|
|
||||||
* encoding.
|
|
||||||
*
|
|
||||||
* @return The unshift character
|
|
||||||
*/
|
|
||||||
abstract byte unshift();
|
|
||||||
}
|
|
@ -1,195 +0,0 @@
|
|||||||
/* ====================================================================
|
|
||||||
* Copyright (c) 2006 J.T. Beetstra
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.beetstra.jutf7;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.nio.charset.CharsetDecoder;
|
|
||||||
import java.nio.charset.CoderResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* The CharsetDecoder used to decode both variants of the UTF-7 charset and the
|
|
||||||
* modified-UTF-7 charset.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author Jaap Beetstra
|
|
||||||
*/
|
|
||||||
class UTF7StyleCharsetDecoder extends CharsetDecoder {
|
|
||||||
private final Base64Util base64;
|
|
||||||
private final byte shift;
|
|
||||||
private final byte unshift;
|
|
||||||
private final boolean strict;
|
|
||||||
private boolean base64mode;
|
|
||||||
private int bitsRead;
|
|
||||||
private int tempChar;
|
|
||||||
private boolean justShifted;
|
|
||||||
private boolean justUnshifted;
|
|
||||||
|
|
||||||
UTF7StyleCharsetDecoder(UTF7StyleCharset cs, Base64Util base64, boolean strict) {
|
|
||||||
super(cs, 0.6f, 1.0f);
|
|
||||||
this.base64 = base64;
|
|
||||||
this.strict = strict;
|
|
||||||
this.shift = cs.shift();
|
|
||||||
this.unshift = cs.unshift();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.nio.charset.CharsetDecoder#decodeLoop(java.nio.ByteBuffer,
|
|
||||||
* java.nio.CharBuffer)
|
|
||||||
*/
|
|
||||||
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
|
|
||||||
while (in.hasRemaining()) {
|
|
||||||
byte b = in.get();
|
|
||||||
if (base64mode) {
|
|
||||||
if (b == unshift) {
|
|
||||||
if (base64bitsWaiting())
|
|
||||||
return malformed(in);
|
|
||||||
if (justShifted) {
|
|
||||||
if (!out.hasRemaining())
|
|
||||||
return overflow(in);
|
|
||||||
out.put((char)shift);
|
|
||||||
} else
|
|
||||||
justUnshifted = true;
|
|
||||||
setUnshifted();
|
|
||||||
} else {
|
|
||||||
if (!out.hasRemaining())
|
|
||||||
return overflow(in);
|
|
||||||
CoderResult result = handleBase64(in, out, b);
|
|
||||||
if (result != null)
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
justShifted = false;
|
|
||||||
} else {
|
|
||||||
if (b == shift) {
|
|
||||||
base64mode = true;
|
|
||||||
if (justUnshifted && strict)
|
|
||||||
return malformed(in);
|
|
||||||
justShifted = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!out.hasRemaining())
|
|
||||||
return overflow(in);
|
|
||||||
out.put((char)b);
|
|
||||||
justUnshifted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CoderResult.UNDERFLOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CoderResult overflow(ByteBuffer in) {
|
|
||||||
in.position(in.position() - 1);
|
|
||||||
return CoderResult.OVERFLOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Decodes a byte in <i>base 64 mode</i>. Will directly write a character to
|
|
||||||
* the output buffer if completed.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param in The input buffer
|
|
||||||
* @param out The output buffer
|
|
||||||
* @param lastRead Last byte read from the input buffer
|
|
||||||
* @return CoderResult.malformed if a non-base 64 character was encountered
|
|
||||||
* in strict mode, null otherwise
|
|
||||||
*/
|
|
||||||
private CoderResult handleBase64(ByteBuffer in, CharBuffer out, byte lastRead) {
|
|
||||||
CoderResult result = null;
|
|
||||||
int sextet = base64.getSextet(lastRead);
|
|
||||||
if (sextet >= 0) {
|
|
||||||
bitsRead += 6;
|
|
||||||
if (bitsRead < 16) {
|
|
||||||
tempChar += sextet << (16 - bitsRead);
|
|
||||||
} else {
|
|
||||||
bitsRead -= 16;
|
|
||||||
tempChar += sextet >> (bitsRead);
|
|
||||||
out.put((char)tempChar);
|
|
||||||
tempChar = (sextet << (16 - bitsRead)) & 0xFFFF;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (strict)
|
|
||||||
return malformed(in);
|
|
||||||
out.put((char)lastRead);
|
|
||||||
if (base64bitsWaiting())
|
|
||||||
result = malformed(in);
|
|
||||||
setUnshifted();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.nio.charset.CharsetDecoder#implFlush(java.nio.CharBuffer)
|
|
||||||
*/
|
|
||||||
protected CoderResult implFlush(CharBuffer out) {
|
|
||||||
if ((base64mode && strict) || base64bitsWaiting())
|
|
||||||
return CoderResult.malformedForLength(1);
|
|
||||||
return CoderResult.UNDERFLOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.nio.charset.CharsetDecoder#implReset()
|
|
||||||
*/
|
|
||||||
protected void implReset() {
|
|
||||||
setUnshifted();
|
|
||||||
justUnshifted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Resets the input buffer position to just before the last byte read, and
|
|
||||||
* returns a result indicating to skip the last byte.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param in The input buffer
|
|
||||||
* @return CoderResult.malformedForLength(1);
|
|
||||||
*/
|
|
||||||
private CoderResult malformed(ByteBuffer in) {
|
|
||||||
in.position(in.position() - 1);
|
|
||||||
return CoderResult.malformedForLength(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return True if there are base64 encoded characters waiting to be written
|
|
||||||
*/
|
|
||||||
private boolean base64bitsWaiting() {
|
|
||||||
return tempChar != 0 || bitsRead >= 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Updates internal state to reflect the decoder is no longer in <i>base 64
|
|
||||||
* mode</i>
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
private void setUnshifted() {
|
|
||||||
base64mode = false;
|
|
||||||
bitsRead = 0;
|
|
||||||
tempChar = 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
/* ====================================================================
|
|
||||||
* Copyright (c) 2006 J.T. Beetstra
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.beetstra.jutf7;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.nio.charset.CharsetEncoder;
|
|
||||||
import java.nio.charset.CoderResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* The CharsetEncoder used to encode both variants of the UTF-7 charset and the
|
|
||||||
* modified-UTF-7 charset.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* <strong>Please note this class does not behave strictly according to the
|
|
||||||
* specification in Sun Java VMs before 1.6.</strong> This is done to get around
|
|
||||||
* a bug in the implementation of
|
|
||||||
* {@link java.nio.charset.CharsetEncoder#encode(CharBuffer)}. Unfortunately,
|
|
||||||
* that method cannot be overridden.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6221056">JDK
|
|
||||||
* bug 6221056< /a>
|
|
||||||
* @author Jaap Beetstra
|
|
||||||
*/
|
|
||||||
class UTF7StyleCharsetEncoder extends CharsetEncoder {
|
|
||||||
private static final float AVG_BYTES_PER_CHAR = 1.5f;
|
|
||||||
private static final float MAX_BYTES_PER_CHAR = 5.0f;
|
|
||||||
private final UTF7StyleCharset cs;
|
|
||||||
private final Base64Util base64;
|
|
||||||
private final byte shift;
|
|
||||||
private final byte unshift;
|
|
||||||
private final boolean strict;
|
|
||||||
private boolean base64mode;
|
|
||||||
private int bitsToOutput;
|
|
||||||
private int sextet;
|
|
||||||
static boolean useUglyHackToForceCallToFlushInJava5;
|
|
||||||
static {
|
|
||||||
String version = System.getProperty("java.specification.version");
|
|
||||||
String vendor = System.getProperty("java.vm.vendor");
|
|
||||||
useUglyHackToForceCallToFlushInJava5 = "1.4".equals(version) || "1.5".equals(version);
|
|
||||||
useUglyHackToForceCallToFlushInJava5 &= "Sun Microsystems Inc.".equals(vendor);
|
|
||||||
}
|
|
||||||
|
|
||||||
UTF7StyleCharsetEncoder(UTF7StyleCharset cs, Base64Util base64, boolean strict) {
|
|
||||||
super(cs, AVG_BYTES_PER_CHAR, MAX_BYTES_PER_CHAR);
|
|
||||||
this.cs = cs;
|
|
||||||
this.base64 = base64;
|
|
||||||
this.strict = strict;
|
|
||||||
this.shift = cs.shift();
|
|
||||||
this.unshift = cs.unshift();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.nio.charset.CharsetEncoder#implReset()
|
|
||||||
*/
|
|
||||||
protected void implReset() {
|
|
||||||
base64mode = false;
|
|
||||||
sextet = 0;
|
|
||||||
bitsToOutput = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
* <p>
|
|
||||||
* Note that this method might return <code>CoderResult.OVERFLOW</code> (as
|
|
||||||
* is required by the specification) if insufficient space is available in
|
|
||||||
* the output buffer. However, calling it again on JDKs before Java 6
|
|
||||||
* triggers a bug in
|
|
||||||
* {@link java.nio.charset.CharsetEncoder#flush(ByteBuffer)} causing it to
|
|
||||||
* throw an IllegalStateException (the buggy method is <code>final</code>,
|
|
||||||
* thus cannot be overridden).
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see <a
|
|
||||||
* href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6227608">
|
|
||||||
* JDK bug 6227608< /a>
|
|
||||||
* @param out The output byte buffer
|
|
||||||
* @return A coder-result object describing the reason for termination
|
|
||||||
*/
|
|
||||||
protected CoderResult implFlush(ByteBuffer out) {
|
|
||||||
if (base64mode) {
|
|
||||||
if (out.remaining() < 2)
|
|
||||||
return CoderResult.OVERFLOW;
|
|
||||||
if (bitsToOutput != 0)
|
|
||||||
out.put(base64.getChar(sextet));
|
|
||||||
out.put(unshift);
|
|
||||||
}
|
|
||||||
return CoderResult.UNDERFLOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
* <p>
|
|
||||||
* Note that this method might return <code>CoderResult.OVERFLOW</code>,
|
|
||||||
* even though there is sufficient space available in the output buffer.
|
|
||||||
* This is done to force the broken implementation of
|
|
||||||
* {@link java.nio.charset.CharsetEncoder#encode(CharBuffer)} to call flush
|
|
||||||
* (the buggy method is <code>final</code>, thus cannot be overridden).
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* However, String.getBytes() fails if CoderResult.OVERFLOW is returned,
|
|
||||||
* since this assumes it always allocates sufficient bytes (maxBytesPerChar
|
|
||||||
* * nr_of_chars). Thus, as an extra check, the size of the input buffer is
|
|
||||||
* compared against the size of the output buffer. A static variable is used
|
|
||||||
* to indicate if a broken java version is used.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* It is not possible to directly write the last few bytes, since more bytes
|
|
||||||
* might be waiting to be encoded then those available in the input buffer.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see <a
|
|
||||||
* href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6221056">
|
|
||||||
* JDK bug 6221056< /a>
|
|
||||||
* @param in The input character buffer
|
|
||||||
* @param out The output byte buffer
|
|
||||||
* @return A coder-result object describing the reason for termination
|
|
||||||
*/
|
|
||||||
protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
|
|
||||||
while (in.hasRemaining()) {
|
|
||||||
if (out.remaining() < 4)
|
|
||||||
return CoderResult.OVERFLOW;
|
|
||||||
char ch = in.get();
|
|
||||||
if (cs.canEncodeDirectly(ch)) {
|
|
||||||
unshift(out, ch);
|
|
||||||
out.put((byte)ch);
|
|
||||||
} else if (!base64mode && ch == shift) {
|
|
||||||
out.put(shift);
|
|
||||||
out.put(unshift);
|
|
||||||
} else
|
|
||||||
encodeBase64(ch, out);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* <HACK type="ugly"> These lines are required to trick JDK 1.5 and
|
|
||||||
* earlier into flushing when using Charset.encode(String),
|
|
||||||
* Charset.encode(CharBuffer) or CharsetEncoder.encode(CharBuffer)
|
|
||||||
* Without them, the last few bytes may be missing.
|
|
||||||
*/
|
|
||||||
if (base64mode && useUglyHackToForceCallToFlushInJava5
|
|
||||||
&& out.limit() != MAX_BYTES_PER_CHAR * in.limit())
|
|
||||||
return CoderResult.OVERFLOW;
|
|
||||||
/* </HACK> */
|
|
||||||
return CoderResult.UNDERFLOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Writes the bytes necessary to leave <i>base 64 mode</i>. This might
|
|
||||||
* include an unshift character.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param out
|
|
||||||
* @param ch
|
|
||||||
*/
|
|
||||||
private void unshift(ByteBuffer out, char ch) {
|
|
||||||
if (!base64mode)
|
|
||||||
return;
|
|
||||||
if (bitsToOutput != 0)
|
|
||||||
out.put(base64.getChar(sextet));
|
|
||||||
if (base64.contains(ch) || ch == unshift || strict)
|
|
||||||
out.put(unshift);
|
|
||||||
base64mode = false;
|
|
||||||
sextet = 0;
|
|
||||||
bitsToOutput = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Writes the bytes necessary to encode a character in <i>base 64 mode</i>.
|
|
||||||
* All bytes which are fully determined will be written. The fields
|
|
||||||
* <code>bitsToOutput</code> and <code>sextet</code> are used to remember
|
|
||||||
* the bytes not yet fully determined.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param out
|
|
||||||
* @param ch
|
|
||||||
*/
|
|
||||||
private void encodeBase64(char ch, ByteBuffer out) {
|
|
||||||
if (!base64mode)
|
|
||||||
out.put(shift);
|
|
||||||
base64mode = true;
|
|
||||||
bitsToOutput += 16;
|
|
||||||
while (bitsToOutput >= 6) {
|
|
||||||
bitsToOutput -= 6;
|
|
||||||
sextet += (ch >> bitsToOutput);
|
|
||||||
sextet &= 0x3F;
|
|
||||||
out.put(base64.getChar(sextet));
|
|
||||||
sextet = 0;
|
|
||||||
}
|
|
||||||
sextet = (ch << (6 - bitsToOutput)) & 0x3F;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,372 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Account stores all of the settings for a single account defined by the user. It is able to save
|
|
||||||
* and delete itself given a Preferences to work with. Each account is defined by a UUID.
|
|
||||||
*/
|
|
||||||
public class Account implements Serializable {
|
|
||||||
public static final int DELETE_POLICY_NEVER = 0;
|
|
||||||
public static final int DELETE_POLICY_7DAYS = 1;
|
|
||||||
public static final int DELETE_POLICY_ON_DELETE = 2;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2975156672298625121L;
|
|
||||||
|
|
||||||
String mUuid;
|
|
||||||
String mStoreUri;
|
|
||||||
String mLocalStoreUri;
|
|
||||||
String mTransportUri;
|
|
||||||
String mDescription;
|
|
||||||
String mName;
|
|
||||||
String mEmail;
|
|
||||||
String mSignature;
|
|
||||||
String mAlwaysBcc;
|
|
||||||
int mAutomaticCheckIntervalMinutes;
|
|
||||||
long mLastAutomaticCheckTime;
|
|
||||||
boolean mNotifyNewMail;
|
|
||||||
String mDraftsFolderName;
|
|
||||||
String mSentFolderName;
|
|
||||||
String mTrashFolderName;
|
|
||||||
String mOutboxFolderName;
|
|
||||||
int mAccountNumber;
|
|
||||||
boolean mVibrate;
|
|
||||||
String mRingtoneUri;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* 0 Never
|
|
||||||
* 1 After 7 days
|
|
||||||
* 2 When I delete from inbox
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
int mDeletePolicy;
|
|
||||||
|
|
||||||
public Account(Context context) {
|
|
||||||
// TODO Change local store path to something readable / recognizable
|
|
||||||
mUuid = UUID.randomUUID().toString();
|
|
||||||
mLocalStoreUri = "local://localhost/" + context.getDatabasePath(mUuid + ".db");
|
|
||||||
mAutomaticCheckIntervalMinutes = -1;
|
|
||||||
mAccountNumber = -1;
|
|
||||||
mNotifyNewMail = true;
|
|
||||||
mSignature = "Sent from my Android phone with K-9. Please excuse my brevity.";
|
|
||||||
mVibrate = false;
|
|
||||||
mRingtoneUri = "content://settings/system/notification_sound";
|
|
||||||
}
|
|
||||||
|
|
||||||
Account(Preferences preferences, String uuid) {
|
|
||||||
this.mUuid = uuid;
|
|
||||||
refresh(preferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the account from the stored settings.
|
|
||||||
*/
|
|
||||||
public void refresh(Preferences preferences) {
|
|
||||||
mStoreUri = Utility.base64Decode(preferences.mSharedPreferences.getString(mUuid
|
|
||||||
+ ".storeUri", null));
|
|
||||||
mLocalStoreUri = preferences.mSharedPreferences.getString(mUuid + ".localStoreUri", null);
|
|
||||||
mTransportUri = Utility.base64Decode(preferences.mSharedPreferences.getString(mUuid
|
|
||||||
+ ".transportUri", null));
|
|
||||||
mDescription = preferences.mSharedPreferences.getString(mUuid + ".description", null);
|
|
||||||
mAlwaysBcc = preferences.mSharedPreferences.getString(mUuid + ".alwaysBcc", mAlwaysBcc);
|
|
||||||
mName = preferences.mSharedPreferences.getString(mUuid + ".name", mName);
|
|
||||||
mEmail = preferences.mSharedPreferences.getString(mUuid + ".email", mEmail);
|
|
||||||
mSignature = preferences.mSharedPreferences.getString(mUuid + ".signature", mSignature);
|
|
||||||
mAutomaticCheckIntervalMinutes = preferences.mSharedPreferences.getInt(mUuid
|
|
||||||
+ ".automaticCheckIntervalMinutes", -1);
|
|
||||||
mLastAutomaticCheckTime = preferences.mSharedPreferences.getLong(mUuid
|
|
||||||
+ ".lastAutomaticCheckTime", 0);
|
|
||||||
mNotifyNewMail = preferences.mSharedPreferences.getBoolean(mUuid + ".notifyNewMail",
|
|
||||||
false);
|
|
||||||
mDeletePolicy = preferences.mSharedPreferences.getInt(mUuid + ".deletePolicy", 0);
|
|
||||||
mDraftsFolderName = preferences.mSharedPreferences.getString(mUuid + ".draftsFolderName",
|
|
||||||
"Drafts");
|
|
||||||
mSentFolderName = preferences.mSharedPreferences.getString(mUuid + ".sentFolderName",
|
|
||||||
"Sent");
|
|
||||||
mTrashFolderName = preferences.mSharedPreferences.getString(mUuid + ".trashFolderName",
|
|
||||||
"Trash");
|
|
||||||
mOutboxFolderName = preferences.mSharedPreferences.getString(mUuid + ".outboxFolderName",
|
|
||||||
"Outbox");
|
|
||||||
mAccountNumber = preferences.mSharedPreferences.getInt(mUuid + ".accountNumber", 0);
|
|
||||||
mVibrate = preferences.mSharedPreferences.getBoolean(mUuid + ".vibrate", false);
|
|
||||||
mRingtoneUri = preferences.mSharedPreferences.getString(mUuid + ".ringtone",
|
|
||||||
"content://settings/system/notification_sound");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUuid() {
|
|
||||||
return mUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStoreUri() {
|
|
||||||
return mStoreUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStoreUri(String storeUri) {
|
|
||||||
this.mStoreUri = storeUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTransportUri() {
|
|
||||||
return mTransportUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTransportUri(String transportUri) {
|
|
||||||
this.mTransportUri = transportUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return mDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription(String description) {
|
|
||||||
this.mDescription = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.mName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSignature() {
|
|
||||||
return mSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignature(String signature) {
|
|
||||||
this.mSignature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return mEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.mEmail = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAlwaysBcc() {
|
|
||||||
return mAlwaysBcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAlwaysBcc(String alwaysBcc) {
|
|
||||||
this.mAlwaysBcc = alwaysBcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isVibrate() {
|
|
||||||
return mVibrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVibrate(boolean vibrate) {
|
|
||||||
mVibrate = vibrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRingtone() {
|
|
||||||
return mRingtoneUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRingtone(String ringtoneUri) {
|
|
||||||
mRingtoneUri = ringtoneUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(Preferences preferences) {
|
|
||||||
String[] uuids = preferences.mSharedPreferences.getString("accountUuids", "").split(",");
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
for (int i = 0, length = uuids.length; i < length; i++) {
|
|
||||||
if (!uuids[i].equals(mUuid)) {
|
|
||||||
if (sb.length() > 0) {
|
|
||||||
sb.append(',');
|
|
||||||
}
|
|
||||||
sb.append(uuids[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String accountUuids = sb.toString();
|
|
||||||
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
|
|
||||||
editor.putString("accountUuids", accountUuids);
|
|
||||||
|
|
||||||
editor.remove(mUuid + ".storeUri");
|
|
||||||
editor.remove(mUuid + ".localStoreUri");
|
|
||||||
editor.remove(mUuid + ".transportUri");
|
|
||||||
editor.remove(mUuid + ".description");
|
|
||||||
editor.remove(mUuid + ".name");
|
|
||||||
editor.remove(mUuid + ".email");
|
|
||||||
editor.remove(mUuid + ".alwaysBcc");
|
|
||||||
editor.remove(mUuid + ".automaticCheckIntervalMinutes");
|
|
||||||
editor.remove(mUuid + ".lastAutomaticCheckTime");
|
|
||||||
editor.remove(mUuid + ".notifyNewMail");
|
|
||||||
editor.remove(mUuid + ".deletePolicy");
|
|
||||||
editor.remove(mUuid + ".draftsFolderName");
|
|
||||||
editor.remove(mUuid + ".sentFolderName");
|
|
||||||
editor.remove(mUuid + ".trashFolderName");
|
|
||||||
editor.remove(mUuid + ".outboxFolderName");
|
|
||||||
editor.remove(mUuid + ".accountNumber");
|
|
||||||
editor.remove(mUuid + ".vibrate");
|
|
||||||
editor.remove(mUuid + ".ringtone");
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(Preferences preferences) {
|
|
||||||
if (!preferences.mSharedPreferences.getString("accountUuids", "").contains(mUuid)) {
|
|
||||||
/*
|
|
||||||
* When the account is first created we assign it a unique account number. The
|
|
||||||
* account number will be unique to that account for the lifetime of the account.
|
|
||||||
* So, we get all the existing account numbers, sort them ascending, loop through
|
|
||||||
* the list and check if the number is greater than 1 + the previous number. If so
|
|
||||||
* we use the previous number + 1 as the account number. This refills gaps.
|
|
||||||
* mAccountNumber starts as -1 on a newly created account. It must be -1 for this
|
|
||||||
* algorithm to work.
|
|
||||||
*
|
|
||||||
* I bet there is a much smarter way to do this. Anyone like to suggest it?
|
|
||||||
*/
|
|
||||||
Account[] accounts = preferences.getAccounts();
|
|
||||||
int[] accountNumbers = new int[accounts.length];
|
|
||||||
for (int i = 0; i < accounts.length; i++) {
|
|
||||||
accountNumbers[i] = accounts[i].getAccountNumber();
|
|
||||||
}
|
|
||||||
Arrays.sort(accountNumbers);
|
|
||||||
for (int accountNumber : accountNumbers) {
|
|
||||||
if (accountNumber > mAccountNumber + 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mAccountNumber = accountNumber;
|
|
||||||
}
|
|
||||||
mAccountNumber++;
|
|
||||||
|
|
||||||
String accountUuids = preferences.mSharedPreferences.getString("accountUuids", "");
|
|
||||||
accountUuids += (accountUuids.length() != 0 ? "," : "") + mUuid;
|
|
||||||
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
|
|
||||||
editor.putString("accountUuids", accountUuids);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
|
|
||||||
|
|
||||||
editor.putString(mUuid + ".storeUri", Utility.base64Encode(mStoreUri));
|
|
||||||
editor.putString(mUuid + ".localStoreUri", mLocalStoreUri);
|
|
||||||
editor.putString(mUuid + ".transportUri", Utility.base64Encode(mTransportUri));
|
|
||||||
editor.putString(mUuid + ".description", mDescription);
|
|
||||||
editor.putString(mUuid + ".name", mName);
|
|
||||||
editor.putString(mUuid + ".email", mEmail);
|
|
||||||
editor.putString(mUuid + ".signature", mSignature);
|
|
||||||
editor.putString(mUuid + ".alwaysBcc", mAlwaysBcc);
|
|
||||||
editor.putInt(mUuid + ".automaticCheckIntervalMinutes", mAutomaticCheckIntervalMinutes);
|
|
||||||
editor.putLong(mUuid + ".lastAutomaticCheckTime", mLastAutomaticCheckTime);
|
|
||||||
editor.putBoolean(mUuid + ".notifyNewMail", mNotifyNewMail);
|
|
||||||
editor.putInt(mUuid + ".deletePolicy", mDeletePolicy);
|
|
||||||
editor.putString(mUuid + ".draftsFolderName", mDraftsFolderName);
|
|
||||||
editor.putString(mUuid + ".sentFolderName", mSentFolderName);
|
|
||||||
editor.putString(mUuid + ".trashFolderName", mTrashFolderName);
|
|
||||||
editor.putString(mUuid + ".outboxFolderName", mOutboxFolderName);
|
|
||||||
editor.putInt(mUuid + ".accountNumber", mAccountNumber);
|
|
||||||
editor.putBoolean(mUuid + ".vibrate", mVibrate);
|
|
||||||
editor.putString(mUuid + ".ringtone", mRingtoneUri);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return mDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri getContentUri() {
|
|
||||||
return Uri.parse("content://accounts/" + getUuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLocalStoreUri() {
|
|
||||||
return mLocalStoreUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocalStoreUri(String localStoreUri) {
|
|
||||||
this.mLocalStoreUri = localStoreUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns -1 for never.
|
|
||||||
*/
|
|
||||||
public int getAutomaticCheckIntervalMinutes() {
|
|
||||||
return mAutomaticCheckIntervalMinutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param automaticCheckIntervalMinutes or -1 for never.
|
|
||||||
*/
|
|
||||||
public void setAutomaticCheckIntervalMinutes(int automaticCheckIntervalMinutes) {
|
|
||||||
this.mAutomaticCheckIntervalMinutes = automaticCheckIntervalMinutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLastAutomaticCheckTime() {
|
|
||||||
return mLastAutomaticCheckTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastAutomaticCheckTime(long lastAutomaticCheckTime) {
|
|
||||||
this.mLastAutomaticCheckTime = lastAutomaticCheckTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNotifyNewMail() {
|
|
||||||
return mNotifyNewMail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotifyNewMail(boolean notifyNewMail) {
|
|
||||||
this.mNotifyNewMail = notifyNewMail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDeletePolicy() {
|
|
||||||
return mDeletePolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDeletePolicy(int deletePolicy) {
|
|
||||||
this.mDeletePolicy = deletePolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDraftsFolderName() {
|
|
||||||
return mDraftsFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDraftsFolderName(String draftsFolderName) {
|
|
||||||
mDraftsFolderName = draftsFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSentFolderName() {
|
|
||||||
return mSentFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSentFolderName(String sentFolderName) {
|
|
||||||
mSentFolderName = sentFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTrashFolderName() {
|
|
||||||
return mTrashFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTrashFolderName(String trashFolderName) {
|
|
||||||
mTrashFolderName = trashFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOutboxFolderName() {
|
|
||||||
return mOutboxFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOutboxFolderName(String outboxFolderName) {
|
|
||||||
mOutboxFolderName = outboxFolderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAccountNumber() {
|
|
||||||
return mAccountNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o instanceof Account) {
|
|
||||||
return ((Account)o).mUuid.equals(mUuid);
|
|
||||||
}
|
|
||||||
return super.equals(o);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2007 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;
|
|
||||||
|
|
||||||
import static android.provider.Contacts.ContactMethods.CONTENT_EMAIL_URI;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.DatabaseUtils;
|
|
||||||
import android.provider.Contacts.ContactMethods;
|
|
||||||
import android.provider.Contacts.People;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ResourceCursorAdapter;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Address;
|
|
||||||
|
|
||||||
public class EmailAddressAdapter extends ResourceCursorAdapter {
|
|
||||||
public static final int NAME_INDEX = 1;
|
|
||||||
|
|
||||||
public static final int DATA_INDEX = 2;
|
|
||||||
|
|
||||||
private static final String SORT_ORDER = People.TIMES_CONTACTED + " DESC, " + People.NAME;
|
|
||||||
|
|
||||||
private ContentResolver mContentResolver;
|
|
||||||
|
|
||||||
private static final String[] PROJECTION = {
|
|
||||||
ContactMethods._ID, // 0
|
|
||||||
ContactMethods.NAME, // 1
|
|
||||||
ContactMethods.DATA
|
|
||||||
// 2
|
|
||||||
};
|
|
||||||
|
|
||||||
public EmailAddressAdapter(Context context) {
|
|
||||||
super(context, R.layout.recipient_dropdown_item, null);
|
|
||||||
mContentResolver = context.getContentResolver();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String convertToString(Cursor cursor) {
|
|
||||||
String name = cursor.getString(NAME_INDEX);
|
|
||||||
String address = cursor.getString(DATA_INDEX);
|
|
||||||
|
|
||||||
return new Address(address, name).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void bindView(View view, Context context, Cursor cursor) {
|
|
||||||
TextView text1 = (TextView)view.findViewById(R.id.text1);
|
|
||||||
TextView text2 = (TextView)view.findViewById(R.id.text2);
|
|
||||||
text1.setText(cursor.getString(NAME_INDEX));
|
|
||||||
text2.setText(cursor.getString(DATA_INDEX));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
|
|
||||||
String where = null;
|
|
||||||
|
|
||||||
if (constraint != null) {
|
|
||||||
String filter = DatabaseUtils.sqlEscapeString(constraint.toString() + '%');
|
|
||||||
|
|
||||||
StringBuilder s = new StringBuilder();
|
|
||||||
s.append("(people.name LIKE ");
|
|
||||||
s.append(filter);
|
|
||||||
s.append(") OR (contact_methods.data LIKE ");
|
|
||||||
s.append(filter);
|
|
||||||
s.append(")");
|
|
||||||
|
|
||||||
where = s.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return mContentResolver.query(CONTENT_EMAIL_URI, PROJECTION, where, null, SORT_ORDER);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Address;
|
|
||||||
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.AutoCompleteTextView.Validator;
|
|
||||||
|
|
||||||
public class EmailAddressValidator implements Validator {
|
|
||||||
public CharSequence fixText(CharSequence invalidText) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid(CharSequence text) {
|
|
||||||
return Address.parse(text.toString()).length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A filtering InputStream that stops allowing reads after the given length has been read. This
|
|
||||||
* is used to allow a client to read directly from an underlying protocol stream without reading
|
|
||||||
* past where the protocol handler intended the client to read.
|
|
||||||
*/
|
|
||||||
public class FixedLengthInputStream extends InputStream {
|
|
||||||
private InputStream mIn;
|
|
||||||
private int mLength;
|
|
||||||
private int mCount;
|
|
||||||
|
|
||||||
public FixedLengthInputStream(InputStream in, int length) {
|
|
||||||
this.mIn = in;
|
|
||||||
this.mLength = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int available() throws IOException {
|
|
||||||
return mLength - mCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (mCount < mLength) {
|
|
||||||
mCount++;
|
|
||||||
return mIn.read();
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int offset, int length) throws IOException {
|
|
||||||
if (mCount < mLength) {
|
|
||||||
int d = mIn.read(b, offset, Math.min(mLength - mCount, length));
|
|
||||||
if (d == -1) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
mCount += d;
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b) throws IOException {
|
|
||||||
return read(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return String.format("FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
/* AUTO-GENERATED FILE. DO NOT MODIFY.
|
|
||||||
*
|
|
||||||
* This class was automatically generated by the
|
|
||||||
* aapt tool from the resource data it found. It
|
|
||||||
* should not be modified by hand.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
public final class Manifest {
|
|
||||||
public static final class permission {
|
|
||||||
public static final String READ_ATTACHMENT="com.fsck.k9.permission.READ_ATTACHMENT";
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,132 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Folder;
|
|
||||||
import com.fsck.k9.mail.Message;
|
|
||||||
import com.fsck.k9.mail.Part;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the interface that MessagingController will use to callback to requesters. This class
|
|
||||||
* is defined as non-abstract so that someone who wants to receive only a few messages can
|
|
||||||
* do so without implementing the entire interface. It is highly recommended that users of
|
|
||||||
* this interface use the @Override annotation in their implementations to avoid being caught by
|
|
||||||
* changes in this class.
|
|
||||||
*/
|
|
||||||
public class MessagingListener {
|
|
||||||
public void listFoldersStarted(Account account) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listFolders(Account account, Folder[] folders) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listFoldersFailed(Account account, String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listFoldersFinished(Account account) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listLocalMessagesStarted(Account account, String folder) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listLocalMessages(Account account, String folder, Message[] messages) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listLocalMessagesFailed(Account account, String folder, String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listLocalMessagesFinished(Account account, String folder) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeMailboxStarted(Account account, String folder) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeMailboxNewMessage(Account account, String folder, Message message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeMailboxRemovedMessage(Account account, String folder,Message message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeMailboxFinished(Account account, String folder,
|
|
||||||
int totalMessagesInMailbox, int numNewMessages) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronizeMailboxFailed(Account account, String folder,
|
|
||||||
String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadMessageForViewStarted(Account account, String folder, String uid) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadMessageForViewHeadersAvailable(Account account, String folder, String uid,
|
|
||||||
Message message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadMessageForViewBodyAvailable(Account account, String folder, String uid,
|
|
||||||
Message message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadMessageForViewFinished(Account account, String folder, String uid,
|
|
||||||
Message message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadMessageForViewFailed(Account account, String folder, String uid, String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkMailStarted(Context context, Account account) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkMailFinished(Context context, Account account) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkMailFailed(Context context, Account account, String reason) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendPendingMessagesCompleted(Account account) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void emptyTrashCompleted(Account account) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void messageUidChanged(Account account, String folder, String oldUid, String newUid) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadAttachmentStarted(
|
|
||||||
Account account,
|
|
||||||
Message message,
|
|
||||||
Part part,
|
|
||||||
Object tag,
|
|
||||||
boolean requiresDownload)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadAttachmentFinished(
|
|
||||||
Account account,
|
|
||||||
Message message,
|
|
||||||
Part part,
|
|
||||||
Object tag)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadAttachmentFailed(
|
|
||||||
Account account,
|
|
||||||
Message message,
|
|
||||||
Part part,
|
|
||||||
Object tag,
|
|
||||||
String reason)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* General notification messages subclasses can override to be notified that the controller
|
|
||||||
* has completed a command. This is useful for turning off progress indicators that may have
|
|
||||||
* been left over from previous commands.
|
|
||||||
* @param moreCommandsToRun True if the controller will continue on to another command
|
|
||||||
* immediately.
|
|
||||||
*/
|
|
||||||
public void controllerCommandCompleted(boolean moreCommandsToRun) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A filtering InputStream that allows single byte "peeks" without consuming the byte. The
|
|
||||||
* client of this stream can call peek() to see the next available byte in the stream
|
|
||||||
* and a subsequent read will still return the peeked byte.
|
|
||||||
*/
|
|
||||||
public class PeekableInputStream extends InputStream {
|
|
||||||
private InputStream mIn;
|
|
||||||
private boolean mPeeked;
|
|
||||||
private int mPeekedByte;
|
|
||||||
|
|
||||||
public PeekableInputStream(InputStream in) {
|
|
||||||
this.mIn = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (!mPeeked) {
|
|
||||||
return mIn.read();
|
|
||||||
} else {
|
|
||||||
mPeeked = false;
|
|
||||||
return mPeekedByte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int peek() throws IOException {
|
|
||||||
if (!mPeeked) {
|
|
||||||
mPeekedByte = read();
|
|
||||||
mPeeked = true;
|
|
||||||
}
|
|
||||||
return mPeekedByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int offset, int length) throws IOException {
|
|
||||||
if (!mPeeked) {
|
|
||||||
return mIn.read(b, offset, length);
|
|
||||||
} else {
|
|
||||||
b[0] = (byte)mPeekedByte;
|
|
||||||
mPeeked = false;
|
|
||||||
int r = mIn.read(b, offset + 1, length - 1);
|
|
||||||
if (r == -1) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return r + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b) throws IOException {
|
|
||||||
return read(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return String.format("PeekableInputStream(in=%s, peeked=%b, peekedByte=%d)",
|
|
||||||
mIn.toString(), mPeeked, mPeekedByte);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class Preferences {
|
|
||||||
private static Preferences preferences;
|
|
||||||
|
|
||||||
SharedPreferences mSharedPreferences;
|
|
||||||
|
|
||||||
private Preferences(Context context) {
|
|
||||||
mSharedPreferences = context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO need to think about what happens if this gets GCed along with the
|
|
||||||
* Activity that initialized it. Do we lose ability to read Preferences in
|
|
||||||
* further Activities? Maybe this should be stored in the Application
|
|
||||||
* context.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static synchronized Preferences getPreferences(Context context) {
|
|
||||||
if (preferences == null) {
|
|
||||||
preferences = new Preferences(context);
|
|
||||||
}
|
|
||||||
return preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of the accounts on the system. If no accounts are
|
|
||||||
* registered the method returns an empty array.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Account[] getAccounts() {
|
|
||||||
String accountUuids = mSharedPreferences.getString("accountUuids", null);
|
|
||||||
if (accountUuids == null || accountUuids.length() == 0) {
|
|
||||||
return new Account[] {};
|
|
||||||
}
|
|
||||||
String[] uuids = accountUuids.split(",");
|
|
||||||
Account[] accounts = new Account[uuids.length];
|
|
||||||
for (int i = 0, length = uuids.length; i < length; i++) {
|
|
||||||
accounts[i] = new Account(this, uuids[i]);
|
|
||||||
}
|
|
||||||
return accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Account getAccountByContentUri(Uri uri) {
|
|
||||||
return new Account(this, uri.getPath().substring(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Account marked as default. If no account is marked as default
|
|
||||||
* the first account in the list is marked as default and then returned. If
|
|
||||||
* there are no accounts on the system the method returns null.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Account getDefaultAccount() {
|
|
||||||
String defaultAccountUuid = mSharedPreferences.getString("defaultAccountUuid", null);
|
|
||||||
Account defaultAccount = null;
|
|
||||||
Account[] accounts = getAccounts();
|
|
||||||
if (defaultAccountUuid != null) {
|
|
||||||
for (Account account : accounts) {
|
|
||||||
if (account.getUuid().equals(defaultAccountUuid)) {
|
|
||||||
defaultAccount = account;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultAccount == null) {
|
|
||||||
if (accounts.length > 0) {
|
|
||||||
defaultAccount = accounts[0];
|
|
||||||
setDefaultAccount(defaultAccount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultAccount(Account account) {
|
|
||||||
mSharedPreferences.edit().putString("defaultAccountUuid", account.getUuid()).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnableDebugLogging(boolean value) {
|
|
||||||
mSharedPreferences.edit().putBoolean("enableDebugLogging", value).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean geteEnableDebugLogging() {
|
|
||||||
return mSharedPreferences.getBoolean("enableDebugLogging", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnableSensitiveLogging(boolean value) {
|
|
||||||
mSharedPreferences.edit().putBoolean("enableSensitiveLogging", value).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getEnableSensitiveLogging() {
|
|
||||||
return mSharedPreferences.getBoolean("enableSensitiveLogging", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
mSharedPreferences.edit().clear().commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dump() {
|
|
||||||
if (Config.LOGV) {
|
|
||||||
for (String key : mSharedPreferences.getAll().keySet()) {
|
|
||||||
Log.v(k9.LOG_TAG, key + " = " + mSharedPreferences.getAll().get(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,452 +0,0 @@
|
|||||||
/* AUTO-GENERATED FILE. DO NOT MODIFY.
|
|
||||||
*
|
|
||||||
* This class was automatically generated by the
|
|
||||||
* aapt tool from the resource data it found. It
|
|
||||||
* should not be modified by hand.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
public final class R {
|
|
||||||
public static final class array {
|
|
||||||
public static final int account_settings_check_frequency_entries=0x7f050000;
|
|
||||||
public static final int account_settings_check_frequency_values=0x7f050001;
|
|
||||||
}
|
|
||||||
public static final class attr {
|
|
||||||
}
|
|
||||||
public static final class color {
|
|
||||||
public static final int folder_message_list_child_background=0x7f070000;
|
|
||||||
}
|
|
||||||
public static final class dimen {
|
|
||||||
public static final int button_minWidth=0x7f080000;
|
|
||||||
}
|
|
||||||
public static final class drawable {
|
|
||||||
public static final int appointment_indicator_leftside_1=0x7f020000;
|
|
||||||
public static final int appointment_indicator_leftside_10=0x7f020001;
|
|
||||||
public static final int appointment_indicator_leftside_11=0x7f020002;
|
|
||||||
public static final int appointment_indicator_leftside_12=0x7f020003;
|
|
||||||
public static final int appointment_indicator_leftside_13=0x7f020004;
|
|
||||||
public static final int appointment_indicator_leftside_14=0x7f020005;
|
|
||||||
public static final int appointment_indicator_leftside_15=0x7f020006;
|
|
||||||
public static final int appointment_indicator_leftside_16=0x7f020007;
|
|
||||||
public static final int appointment_indicator_leftside_17=0x7f020008;
|
|
||||||
public static final int appointment_indicator_leftside_18=0x7f020009;
|
|
||||||
public static final int appointment_indicator_leftside_19=0x7f02000a;
|
|
||||||
public static final int appointment_indicator_leftside_2=0x7f02000b;
|
|
||||||
public static final int appointment_indicator_leftside_20=0x7f02000c;
|
|
||||||
public static final int appointment_indicator_leftside_21=0x7f02000d;
|
|
||||||
public static final int appointment_indicator_leftside_3=0x7f02000e;
|
|
||||||
public static final int appointment_indicator_leftside_4=0x7f02000f;
|
|
||||||
public static final int appointment_indicator_leftside_5=0x7f020010;
|
|
||||||
public static final int appointment_indicator_leftside_6=0x7f020011;
|
|
||||||
public static final int appointment_indicator_leftside_7=0x7f020012;
|
|
||||||
public static final int appointment_indicator_leftside_8=0x7f020013;
|
|
||||||
public static final int appointment_indicator_leftside_9=0x7f020014;
|
|
||||||
public static final int attached_image_placeholder=0x7f020015;
|
|
||||||
public static final int bottombar_565=0x7f020016;
|
|
||||||
public static final int btn_dialog=0x7f020017;
|
|
||||||
public static final int btn_dialog_disable=0x7f020018;
|
|
||||||
public static final int btn_dialog_disable_focused=0x7f020019;
|
|
||||||
public static final int btn_dialog_normal=0x7f02001a;
|
|
||||||
public static final int btn_dialog_pressed=0x7f02001b;
|
|
||||||
public static final int btn_dialog_selected=0x7f02001c;
|
|
||||||
public static final int button_indicator_next=0x7f02001d;
|
|
||||||
public static final int divider_horizontal_email=0x7f02001e;
|
|
||||||
public static final int email_quoted_bar=0x7f02001f;
|
|
||||||
public static final int expander_ic_folder=0x7f020020;
|
|
||||||
public static final int expander_ic_folder_maximized=0x7f020021;
|
|
||||||
public static final int expander_ic_folder_minimized=0x7f020022;
|
|
||||||
public static final int folder_message_list_child_background=0x7f020023;
|
|
||||||
public static final int ic_delete=0x7f020024;
|
|
||||||
public static final int ic_email_attachment=0x7f020025;
|
|
||||||
public static final int ic_email_attachment_small=0x7f020026;
|
|
||||||
public static final int ic_email_caret_double_light=0x7f020027;
|
|
||||||
public static final int ic_email_caret_single_light=0x7f020028;
|
|
||||||
public static final int ic_email_thread_open_bottom_default=0x7f020029;
|
|
||||||
public static final int ic_email_thread_open_top_default=0x7f02002a;
|
|
||||||
public static final int ic_menu_account_list=0x7f02002b;
|
|
||||||
public static final int ic_menu_add=0x7f02002c;
|
|
||||||
public static final int ic_menu_archive=0x7f02002d;
|
|
||||||
public static final int ic_menu_attachment=0x7f02002e;
|
|
||||||
public static final int ic_menu_cc=0x7f02002f;
|
|
||||||
public static final int ic_menu_close_clear_cancel=0x7f020030;
|
|
||||||
public static final int ic_menu_compose=0x7f020031;
|
|
||||||
public static final int ic_menu_delete=0x7f020032;
|
|
||||||
public static final int ic_menu_edit=0x7f020033;
|
|
||||||
public static final int ic_menu_forward_mail=0x7f020034;
|
|
||||||
public static final int ic_menu_inbox=0x7f020035;
|
|
||||||
public static final int ic_menu_mark=0x7f020036;
|
|
||||||
public static final int ic_menu_navigate=0x7f020037;
|
|
||||||
public static final int ic_menu_preferences=0x7f020038;
|
|
||||||
public static final int ic_menu_refresh=0x7f020039;
|
|
||||||
public static final int ic_menu_reply=0x7f02003a;
|
|
||||||
public static final int ic_menu_reply_all=0x7f02003b;
|
|
||||||
public static final int ic_menu_save_draft=0x7f02003c;
|
|
||||||
public static final int ic_menu_search=0x7f02003d;
|
|
||||||
public static final int ic_menu_send=0x7f02003e;
|
|
||||||
public static final int ic_mms_attachment_small=0x7f02003f;
|
|
||||||
public static final int icon=0x7f020040;
|
|
||||||
public static final int stat_notify_email_generic=0x7f020041;
|
|
||||||
public static final int text_box=0x7f020042;
|
|
||||||
public static final int text_box_light=0x7f020043;
|
|
||||||
}
|
|
||||||
public static final class id {
|
|
||||||
public static final int account_always_bcc=0x7f0a000b;
|
|
||||||
public static final int account_check_frequency=0x7f0a0018;
|
|
||||||
public static final int account_default=0x7f0a0005;
|
|
||||||
public static final int account_delete_policy=0x7f0a0013;
|
|
||||||
public static final int account_delete_policy_label=0x7f0a0012;
|
|
||||||
public static final int account_description=0x7f0a0016;
|
|
||||||
public static final int account_email=0x7f0a0002;
|
|
||||||
public static final int account_name=0x7f0a000a;
|
|
||||||
public static final int account_notify=0x7f0a0019;
|
|
||||||
public static final int account_password=0x7f0a0003;
|
|
||||||
public static final int account_port=0x7f0a0010;
|
|
||||||
public static final int account_require_login=0x7f0a001a;
|
|
||||||
public static final int account_require_login_settings=0x7f0a001b;
|
|
||||||
public static final int account_security_type=0x7f0a0011;
|
|
||||||
public static final int account_server=0x7f0a000f;
|
|
||||||
public static final int account_server_label=0x7f0a000e;
|
|
||||||
public static final int account_settings=0x7f0a004e;
|
|
||||||
public static final int account_signature=0x7f0a000c;
|
|
||||||
public static final int account_username=0x7f0a000d;
|
|
||||||
public static final int accounts=0x7f0a004d;
|
|
||||||
public static final int add_attachment=0x7f0a0053;
|
|
||||||
public static final int add_cc_bcc=0x7f0a004f;
|
|
||||||
public static final int add_new_account=0x7f0a001d;
|
|
||||||
public static final int attachment=0x7f0a003d;
|
|
||||||
public static final int attachment_delete=0x7f0a0033;
|
|
||||||
public static final int attachment_icon=0x7f0a0039;
|
|
||||||
public static final int attachment_info=0x7f0a003a;
|
|
||||||
public static final int attachment_name=0x7f0a0034;
|
|
||||||
public static final int attachments=0x7f0a002e;
|
|
||||||
public static final int bcc=0x7f0a002d;
|
|
||||||
public static final int cancel=0x7f0a0009;
|
|
||||||
public static final int cc=0x7f0a002c;
|
|
||||||
public static final int check_mail=0x7f0a0047;
|
|
||||||
public static final int chip=0x7f0a0024;
|
|
||||||
public static final int compose=0x7f0a0048;
|
|
||||||
public static final int date=0x7f0a0026;
|
|
||||||
public static final int debug_logging=0x7f0a0022;
|
|
||||||
public static final int delete=0x7f0a0038;
|
|
||||||
public static final int delete_account=0x7f0a0046;
|
|
||||||
public static final int description=0x7f0a001e;
|
|
||||||
public static final int discard=0x7f0a0052;
|
|
||||||
public static final int done=0x7f0a0017;
|
|
||||||
public static final int download=0x7f0a003b;
|
|
||||||
public static final int dump_settings=0x7f0a0049;
|
|
||||||
public static final int edit_account=0x7f0a0045;
|
|
||||||
public static final int email=0x7f0a001f;
|
|
||||||
public static final int empty=0x7f0a001c;
|
|
||||||
public static final int folder_name=0x7f0a0029;
|
|
||||||
public static final int folder_status=0x7f0a002a;
|
|
||||||
public static final int forward=0x7f0a004a;
|
|
||||||
public static final int from=0x7f0a0025;
|
|
||||||
public static final int imap=0x7f0a0001;
|
|
||||||
public static final int imap_path_prefix=0x7f0a0015;
|
|
||||||
public static final int imap_path_prefix_section=0x7f0a0014;
|
|
||||||
public static final int main_text=0x7f0a0028;
|
|
||||||
public static final int manual_setup=0x7f0a0006;
|
|
||||||
public static final int mark_as_read=0x7f0a004b;
|
|
||||||
public static final int mark_as_unread=0x7f0a0054;
|
|
||||||
public static final int message=0x7f0a0007;
|
|
||||||
public static final int message_content=0x7f0a002f;
|
|
||||||
public static final int new_message_count=0x7f0a0020;
|
|
||||||
public static final int next=0x7f0a0004;
|
|
||||||
public static final int open=0x7f0a0044;
|
|
||||||
public static final int pop=0x7f0a0000;
|
|
||||||
public static final int previous=0x7f0a0035;
|
|
||||||
public static final int progress=0x7f0a0008;
|
|
||||||
public static final int quoted_text=0x7f0a0032;
|
|
||||||
public static final int quoted_text_bar=0x7f0a0030;
|
|
||||||
public static final int quoted_text_delete=0x7f0a0031;
|
|
||||||
public static final int refresh=0x7f0a004c;
|
|
||||||
public static final int reply=0x7f0a0036;
|
|
||||||
public static final int reply_all=0x7f0a0037;
|
|
||||||
public static final int save=0x7f0a0051;
|
|
||||||
public static final int send=0x7f0a0050;
|
|
||||||
public static final int sensitive_logging=0x7f0a0023;
|
|
||||||
public static final int show_pictures=0x7f0a0041;
|
|
||||||
public static final int show_pictures_section=0x7f0a0040;
|
|
||||||
public static final int subject=0x7f0a0027;
|
|
||||||
public static final int text1=0x7f0a0042;
|
|
||||||
public static final int text2=0x7f0a0043;
|
|
||||||
public static final int to=0x7f0a002b;
|
|
||||||
public static final int to_container=0x7f0a003e;
|
|
||||||
public static final int to_label=0x7f0a003f;
|
|
||||||
public static final int version=0x7f0a0021;
|
|
||||||
public static final int view=0x7f0a003c;
|
|
||||||
}
|
|
||||||
public static final class layout {
|
|
||||||
public static final int account_setup_account_type=0x7f030000;
|
|
||||||
public static final int account_setup_basics=0x7f030001;
|
|
||||||
public static final int account_setup_check_settings=0x7f030002;
|
|
||||||
public static final int account_setup_composition=0x7f030003;
|
|
||||||
public static final int account_setup_incoming=0x7f030004;
|
|
||||||
public static final int account_setup_names=0x7f030005;
|
|
||||||
public static final int account_setup_options=0x7f030006;
|
|
||||||
public static final int account_setup_outgoing=0x7f030007;
|
|
||||||
public static final int accounts=0x7f030008;
|
|
||||||
public static final int accounts_item=0x7f030009;
|
|
||||||
public static final int debug=0x7f03000a;
|
|
||||||
public static final int folder_message_list_child=0x7f03000b;
|
|
||||||
public static final int folder_message_list_child_footer=0x7f03000c;
|
|
||||||
public static final int folder_message_list_group=0x7f03000d;
|
|
||||||
public static final int message_compose=0x7f03000e;
|
|
||||||
public static final int message_compose_attachment=0x7f03000f;
|
|
||||||
public static final int message_view=0x7f030010;
|
|
||||||
public static final int message_view_attachment=0x7f030011;
|
|
||||||
public static final int message_view_header=0x7f030012;
|
|
||||||
public static final int recipient_dropdown_item=0x7f030013;
|
|
||||||
}
|
|
||||||
public static final class menu {
|
|
||||||
public static final int accounts_context=0x7f090000;
|
|
||||||
public static final int accounts_option=0x7f090001;
|
|
||||||
public static final int debug_option=0x7f090002;
|
|
||||||
public static final int folder_message_list_context=0x7f090003;
|
|
||||||
public static final int folder_message_list_option=0x7f090004;
|
|
||||||
public static final int message_compose_option=0x7f090005;
|
|
||||||
public static final int message_view_option=0x7f090006;
|
|
||||||
}
|
|
||||||
public static final class string {
|
|
||||||
public static final int account_delete_dlg_instructions_fmt=0x7f0600c8;
|
|
||||||
public static final int account_delete_dlg_title=0x7f0600c7;
|
|
||||||
public static final int account_settings_action=0x7f06001b;
|
|
||||||
public static final int account_settings_add_account_label=0x7f0600b9;
|
|
||||||
public static final int account_settings_always_bcc_label=0x7f0600c3;
|
|
||||||
public static final int account_settings_always_bcc_summary=0x7f0600c4;
|
|
||||||
public static final int account_settings_composition_label=0x7f0600c2;
|
|
||||||
public static final int account_settings_composition_title=0x7f0600c1;
|
|
||||||
public static final int account_settings_default=0x7f0600ad;
|
|
||||||
public static final int account_settings_default_label=0x7f0600ae;
|
|
||||||
public static final int account_settings_default_summary=0x7f0600af;
|
|
||||||
public static final int account_settings_description_label=0x7f0600ba;
|
|
||||||
public static final int account_settings_email_label=0x7f0600b1;
|
|
||||||
public static final int account_settings_incoming_label=0x7f0600b5;
|
|
||||||
public static final int account_settings_incoming_summary=0x7f0600b6;
|
|
||||||
public static final int account_settings_mail_check_frequency_label=0x7f0600b4;
|
|
||||||
public static final int account_settings_name_label=0x7f0600bb;
|
|
||||||
public static final int account_settings_notifications=0x7f0600bc;
|
|
||||||
public static final int account_settings_notify_label=0x7f0600b0;
|
|
||||||
public static final int account_settings_notify_summary=0x7f0600b2;
|
|
||||||
public static final int account_settings_outgoing_label=0x7f0600b7;
|
|
||||||
public static final int account_settings_outgoing_summary=0x7f0600b8;
|
|
||||||
public static final int account_settings_ringtone=0x7f0600bf;
|
|
||||||
public static final int account_settings_servers=0x7f0600c0;
|
|
||||||
public static final int account_settings_show_combined_label=0x7f0600b3;
|
|
||||||
public static final int account_settings_signature_label=0x7f0600c5;
|
|
||||||
public static final int account_settings_signature_summary=0x7f0600c6;
|
|
||||||
public static final int account_settings_title_fmt=0x7f0600ac;
|
|
||||||
public static final int account_settings_vibrate_enable=0x7f0600bd;
|
|
||||||
public static final int account_settings_vibrate_summary=0x7f0600be;
|
|
||||||
public static final int account_setup_account_type_imap_action=0x7f060079;
|
|
||||||
public static final int account_setup_account_type_instructions=0x7f060077;
|
|
||||||
public static final int account_setup_account_type_pop_action=0x7f060078;
|
|
||||||
public static final int account_setup_account_type_title=0x7f060076;
|
|
||||||
public static final int account_setup_basics_default_label=0x7f060069;
|
|
||||||
public static final int account_setup_basics_email_error_duplicate_fmt=0x7f060067;
|
|
||||||
public static final int account_setup_basics_email_error_invalid_fmt=0x7f060066;
|
|
||||||
public static final int account_setup_basics_email_hint=0x7f060065;
|
|
||||||
public static final int account_setup_basics_instructions=0x7f060063;
|
|
||||||
public static final int account_setup_basics_instructions2_fmt=0x7f060064;
|
|
||||||
public static final int account_setup_basics_manual_setup_action=0x7f06006a;
|
|
||||||
public static final int account_setup_basics_password_hint=0x7f060068;
|
|
||||||
public static final int account_setup_basics_title=0x7f060062;
|
|
||||||
public static final int account_setup_check_settings_canceling_msg=0x7f060070;
|
|
||||||
public static final int account_setup_check_settings_check_incoming_msg=0x7f06006d;
|
|
||||||
public static final int account_setup_check_settings_check_outgoing_msg=0x7f06006e;
|
|
||||||
public static final int account_setup_check_settings_finishing_msg=0x7f06006f;
|
|
||||||
public static final int account_setup_check_settings_retr_info_msg=0x7f06006c;
|
|
||||||
public static final int account_setup_check_settings_title=0x7f06006b;
|
|
||||||
public static final int account_setup_failed_dlg_auth_message_fmt=0x7f0600a8;
|
|
||||||
/** Username or password incorrect\n(ERR01 Account does not exist)
|
|
||||||
*/
|
|
||||||
public static final int account_setup_failed_dlg_certificate_message_fmt=0x7f0600a9;
|
|
||||||
/** Cannot connect to server\n(Connection timed out)
|
|
||||||
*/
|
|
||||||
public static final int account_setup_failed_dlg_edit_details_action=0x7f0600ab;
|
|
||||||
/** Cannot safely connect to server\n(Invalid certificate)
|
|
||||||
*/
|
|
||||||
public static final int account_setup_failed_dlg_server_message_fmt=0x7f0600aa;
|
|
||||||
public static final int account_setup_failed_dlg_title=0x7f0600a7;
|
|
||||||
public static final int account_setup_finished_toast=0x7f060075;
|
|
||||||
public static final int account_setup_incoming_delete_policy_7days_label=0x7f060088;
|
|
||||||
public static final int account_setup_incoming_delete_policy_delete_label=0x7f060089;
|
|
||||||
public static final int account_setup_incoming_delete_policy_label=0x7f060086;
|
|
||||||
public static final int account_setup_incoming_delete_policy_never_label=0x7f060087;
|
|
||||||
public static final int account_setup_incoming_imap_path_prefix_hint=0x7f06008b;
|
|
||||||
public static final int account_setup_incoming_imap_path_prefix_label=0x7f06008a;
|
|
||||||
public static final int account_setup_incoming_imap_server_label=0x7f06007e;
|
|
||||||
public static final int account_setup_incoming_password_label=0x7f06007c;
|
|
||||||
public static final int account_setup_incoming_pop_server_label=0x7f06007d;
|
|
||||||
public static final int account_setup_incoming_port_label=0x7f06007f;
|
|
||||||
public static final int account_setup_incoming_security_label=0x7f060080;
|
|
||||||
public static final int account_setup_incoming_security_none_label=0x7f060081;
|
|
||||||
public static final int account_setup_incoming_security_ssl_label=0x7f060083;
|
|
||||||
public static final int account_setup_incoming_security_ssl_optional_label=0x7f060082;
|
|
||||||
public static final int account_setup_incoming_security_tls_label=0x7f060085;
|
|
||||||
public static final int account_setup_incoming_security_tls_optional_label=0x7f060084;
|
|
||||||
public static final int account_setup_incoming_title=0x7f06007a;
|
|
||||||
public static final int account_setup_incoming_username_label=0x7f06007b;
|
|
||||||
public static final int account_setup_names_account_name_label=0x7f060073;
|
|
||||||
public static final int account_setup_names_instructions=0x7f060072;
|
|
||||||
public static final int account_setup_names_title=0x7f060071;
|
|
||||||
public static final int account_setup_names_user_name_label=0x7f060074;
|
|
||||||
public static final int account_setup_options_default_label=0x7f0600a5;
|
|
||||||
public static final int account_setup_options_mail_check_frequency_10min=0x7f0600a1;
|
|
||||||
public static final int account_setup_options_mail_check_frequency_15min=0x7f0600a2;
|
|
||||||
public static final int account_setup_options_mail_check_frequency_1hour=0x7f0600a4;
|
|
||||||
public static final int account_setup_options_mail_check_frequency_30min=0x7f0600a3;
|
|
||||||
public static final int account_setup_options_mail_check_frequency_5min=0x7f0600a0;
|
|
||||||
public static final int account_setup_options_mail_check_frequency_label=0x7f06009e;
|
|
||||||
/** Frequency also used in account_settings_*
|
|
||||||
*/
|
|
||||||
public static final int account_setup_options_mail_check_frequency_never=0x7f06009f;
|
|
||||||
public static final int account_setup_options_notify_label=0x7f0600a6;
|
|
||||||
public static final int account_setup_options_title=0x7f06009d;
|
|
||||||
public static final int account_setup_outgoing_authentication_basic_label=0x7f060098;
|
|
||||||
public static final int account_setup_outgoing_authentication_basic_password_label=0x7f06009a;
|
|
||||||
public static final int account_setup_outgoing_authentication_basic_username_label=0x7f060099;
|
|
||||||
public static final int account_setup_outgoing_authentication_imap_before_smtp_label=0x7f06009c;
|
|
||||||
/** The authentication strings below are for a planned (hopefully) change to the above username and password options
|
|
||||||
*/
|
|
||||||
public static final int account_setup_outgoing_authentication_label=0x7f060097;
|
|
||||||
public static final int account_setup_outgoing_authentication_pop_before_smtp_label=0x7f06009b;
|
|
||||||
public static final int account_setup_outgoing_password_label=0x7f060096;
|
|
||||||
public static final int account_setup_outgoing_port_label=0x7f06008e;
|
|
||||||
public static final int account_setup_outgoing_require_login_label=0x7f060094;
|
|
||||||
public static final int account_setup_outgoing_security_label=0x7f06008f;
|
|
||||||
public static final int account_setup_outgoing_security_none_label=0x7f060090;
|
|
||||||
public static final int account_setup_outgoing_security_ssl_label=0x7f060091;
|
|
||||||
public static final int account_setup_outgoing_security_tls_label=0x7f060093;
|
|
||||||
public static final int account_setup_outgoing_security_tls_optional_label=0x7f060092;
|
|
||||||
public static final int account_setup_outgoing_smtp_server_label=0x7f06008d;
|
|
||||||
public static final int account_setup_outgoing_title=0x7f06008c;
|
|
||||||
public static final int account_setup_outgoing_username_label=0x7f060095;
|
|
||||||
public static final int accounts_action=0x7f06001d;
|
|
||||||
public static final int accounts_context_menu_title=0x7f060029;
|
|
||||||
public static final int accounts_title=0x7f060004;
|
|
||||||
public static final int accounts_welcome=0x7f06003b;
|
|
||||||
public static final int add_account_action=0x7f060016;
|
|
||||||
public static final int add_attachment_action=0x7f060026;
|
|
||||||
public static final int add_cc_bcc_action=0x7f060024;
|
|
||||||
public static final int app_name=0x7f060003;
|
|
||||||
public static final int build_number=0x7f060000;
|
|
||||||
/** User to confirm acceptance of dialog boxes, warnings, errors, etc.
|
|
||||||
*/
|
|
||||||
public static final int cancel_action=0x7f060009;
|
|
||||||
public static final int combined_inbox_label=0x7f060041;
|
|
||||||
public static final int combined_inbox_list_title=0x7f060042;
|
|
||||||
public static final int combined_inbox_title=0x7f060040;
|
|
||||||
public static final int compose_action=0x7f060017;
|
|
||||||
public static final int compose_title=0x7f060005;
|
|
||||||
public static final int continue_action=0x7f06000f;
|
|
||||||
public static final int debug_enable_debug_logging_label=0x7f06003d;
|
|
||||||
public static final int debug_enable_sensitive_logging_label=0x7f06003e;
|
|
||||||
public static final int debug_title=0x7f060006;
|
|
||||||
public static final int debug_version_fmt=0x7f06003c;
|
|
||||||
public static final int delete_action=0x7f06000d;
|
|
||||||
public static final int discard_action=0x7f060012;
|
|
||||||
public static final int done_action=0x7f060010;
|
|
||||||
public static final int dump_settings_action=0x7f060027;
|
|
||||||
public static final int edit_subject_action=0x7f060025;
|
|
||||||
public static final int empty_trash_action=0x7f060028;
|
|
||||||
public static final int folders_action=0x7f060022;
|
|
||||||
public static final int forward_action=0x7f06000e;
|
|
||||||
public static final int general_no_subject=0x7f06002a;
|
|
||||||
public static final int mailbox_select_dlg_new_mailbox_action=0x7f06005b;
|
|
||||||
public static final int mailbox_select_dlg_title=0x7f06005a;
|
|
||||||
public static final int mark_as_read_action=0x7f06001f;
|
|
||||||
public static final int mark_as_unread_action=0x7f060020;
|
|
||||||
public static final int message_compose_attachments_skipped_toast=0x7f06004e;
|
|
||||||
public static final int message_compose_bcc_hint=0x7f060047;
|
|
||||||
public static final int message_compose_cc_hint=0x7f060046;
|
|
||||||
public static final int message_compose_downloading_attachments_toast=0x7f06004d;
|
|
||||||
public static final int message_compose_error_no_recipients=0x7f06004c;
|
|
||||||
public static final int message_compose_fwd_header_fmt=0x7f060049;
|
|
||||||
public static final int message_compose_quoted_text_label=0x7f06004b;
|
|
||||||
public static final int message_compose_reply_header_fmt=0x7f06004a;
|
|
||||||
public static final int message_compose_subject_hint=0x7f060048;
|
|
||||||
public static final int message_compose_to_hint=0x7f060045;
|
|
||||||
public static final int message_copied_toast=0x7f06005d;
|
|
||||||
public static final int message_deleted_toast=0x7f06005f;
|
|
||||||
public static final int message_discarded_toast=0x7f060060;
|
|
||||||
public static final int message_header_mua=0x7f06003f;
|
|
||||||
/** Inbox (12)
|
|
||||||
*/
|
|
||||||
public static final int message_list_load_more_messages_action=0x7f060044;
|
|
||||||
/** Inbox here should be the same as mailbox_name_inbox
|
|
||||||
*/
|
|
||||||
public static final int message_list_title_fmt=0x7f060043;
|
|
||||||
public static final int message_moved_toast=0x7f06005e;
|
|
||||||
public static final int message_saved_toast=0x7f060061;
|
|
||||||
public static final int message_view_attachment_download_action=0x7f060051;
|
|
||||||
public static final int message_view_attachment_view_action=0x7f060050;
|
|
||||||
public static final int message_view_datetime_fmt=0x7f060054;
|
|
||||||
public static final int message_view_fetching_attachment_toast=0x7f060059;
|
|
||||||
public static final int message_view_next_action=0x7f060053;
|
|
||||||
public static final int message_view_prev_action=0x7f060052;
|
|
||||||
public static final int message_view_show_pictures_action=0x7f060058;
|
|
||||||
public static final int message_view_show_pictures_instructions=0x7f060057;
|
|
||||||
public static final int message_view_status_attachment_not_saved=0x7f060056;
|
|
||||||
public static final int message_view_status_attachment_saved=0x7f060055;
|
|
||||||
public static final int message_view_to_label=0x7f06004f;
|
|
||||||
public static final int move_to_action=0x7f060021;
|
|
||||||
public static final int new_mailbox_dlg_title=0x7f06005c;
|
|
||||||
/** Actions will be used as buttons and in menu items
|
|
||||||
*/
|
|
||||||
public static final int next_action=0x7f060007;
|
|
||||||
/** 279 Unread (someone@google.com)
|
|
||||||
*/
|
|
||||||
public static final int notification_new_multi_account_fmt=0x7f060034;
|
|
||||||
public static final int notification_new_one_account_fmt=0x7f060033;
|
|
||||||
public static final int notification_new_scrolling=0x7f060032;
|
|
||||||
public static final int notification_new_title=0x7f060031;
|
|
||||||
public static final int notification_unsent_title=0x7f060035;
|
|
||||||
/** Used as part of a multi-step process
|
|
||||||
*/
|
|
||||||
public static final int okay_action=0x7f060008;
|
|
||||||
public static final int open_action=0x7f06001a;
|
|
||||||
public static final int preferences_action=0x7f060019;
|
|
||||||
public static final int provider_note_live=0x7f0600ca;
|
|
||||||
public static final int provider_note_yahoo=0x7f0600c9;
|
|
||||||
public static final int read_action=0x7f06001e;
|
|
||||||
public static final int read_attachment_desc=0x7f060002;
|
|
||||||
public static final int read_attachment_label=0x7f060001;
|
|
||||||
public static final int refresh_action=0x7f060015;
|
|
||||||
public static final int remove_account_action=0x7f06001c;
|
|
||||||
/** Used to complete a multi-step process
|
|
||||||
*/
|
|
||||||
public static final int remove_action=0x7f060011;
|
|
||||||
public static final int reply_action=0x7f06000b;
|
|
||||||
public static final int reply_all_action=0x7f06000c;
|
|
||||||
public static final int retry_action=0x7f060014;
|
|
||||||
public static final int save_draft_action=0x7f060013;
|
|
||||||
public static final int search_action=0x7f060018;
|
|
||||||
public static final int send_action=0x7f06000a;
|
|
||||||
/** The following mailbox names will be used if the user has not specified one from the server
|
|
||||||
*/
|
|
||||||
public static final int special_mailbox_name_drafts=0x7f060038;
|
|
||||||
public static final int special_mailbox_name_inbox=0x7f060036;
|
|
||||||
public static final int special_mailbox_name_outbox=0x7f060037;
|
|
||||||
public static final int special_mailbox_name_sent=0x7f06003a;
|
|
||||||
public static final int special_mailbox_name_trash=0x7f060039;
|
|
||||||
public static final int status_error=0x7f06002e;
|
|
||||||
/** Shown in place of the subject when a message has no subject. Showing this in parentheses is customary.
|
|
||||||
*/
|
|
||||||
public static final int status_loading=0x7f06002b;
|
|
||||||
public static final int status_loading_more=0x7f06002c;
|
|
||||||
/** Used in Outbox when a message is currently sending
|
|
||||||
*/
|
|
||||||
public static final int status_loading_more_failed=0x7f060030;
|
|
||||||
public static final int status_network_error=0x7f06002d;
|
|
||||||
/** Used in Outbox when a message has failed to send
|
|
||||||
*/
|
|
||||||
public static final int status_sending=0x7f06002f;
|
|
||||||
public static final int view_hide_details_action=0x7f060023;
|
|
||||||
}
|
|
||||||
public static final class xml {
|
|
||||||
public static final int account_settings_preferences=0x7f040000;
|
|
||||||
public static final int providers=0x7f040001;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,176 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.fsck.k9.codec.binary.Base64;
|
|
||||||
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
public class Utility {
|
|
||||||
public final static String readInputStream(InputStream in, String encoding) throws IOException {
|
|
||||||
InputStreamReader reader = new InputStreamReader(in, encoding);
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
int count;
|
|
||||||
char[] buf = new char[512];
|
|
||||||
while ((count = reader.read(buf)) != -1) {
|
|
||||||
sb.append(buf, 0, count);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final static boolean arrayContains(Object[] a, Object o) {
|
|
||||||
for (int i = 0, count = a.length; i < count; i++) {
|
|
||||||
if (a[i].equals(o)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combines the given array of Objects into a single string using the
|
|
||||||
* seperator character and each Object's toString() method. between each
|
|
||||||
* part.
|
|
||||||
*
|
|
||||||
* @param parts
|
|
||||||
* @param seperator
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String combine(Object[] parts, char seperator) {
|
|
||||||
if (parts == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
for (int i = 0; i < parts.length; i++) {
|
|
||||||
sb.append(parts[i].toString());
|
|
||||||
if (i < parts.length - 1) {
|
|
||||||
sb.append(seperator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String base64Decode(String encoded) {
|
|
||||||
if (encoded == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] decoded = new Base64().decode(encoded.getBytes());
|
|
||||||
return new String(decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String base64Encode(String s) {
|
|
||||||
if (s == null) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
byte[] encoded = new Base64().encode(s.getBytes());
|
|
||||||
return new String(encoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean requiredFieldValid(TextView view) {
|
|
||||||
return view.getText() != null && view.getText().length() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean requiredFieldValid(Editable s) {
|
|
||||||
return s != null && s.length() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
|
|
||||||
* double quote character to start and end if it's not already there.
|
|
||||||
* sample -> "sample"
|
|
||||||
* "sample" -> "sample"
|
|
||||||
* ""sample"" -> "sample"
|
|
||||||
* "sample"" -> "sample"
|
|
||||||
* sa"mp"le -> "sa"mp"le"
|
|
||||||
* "sa"mp"le" -> "sa"mp"le"
|
|
||||||
* (empty string) -> ""
|
|
||||||
* " -> ""
|
|
||||||
* @param s
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String quoteString(String s) {
|
|
||||||
if (s == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!s.matches("^\".*\"$")) {
|
|
||||||
return "\"" + s + "\"";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A fast version of URLDecoder.decode() that works only with UTF-8 and does only two
|
|
||||||
* allocations. This version is around 3x as fast as the standard one and I'm using it
|
|
||||||
* hundreds of times in places that slow down the UI, so it helps.
|
|
||||||
*/
|
|
||||||
public static String fastUrlDecode(String s) {
|
|
||||||
try {
|
|
||||||
byte[] bytes = s.getBytes("UTF-8");
|
|
||||||
byte ch;
|
|
||||||
int length = 0;
|
|
||||||
for (int i = 0, count = bytes.length; i < count; i++) {
|
|
||||||
ch = bytes[i];
|
|
||||||
if (ch == '%') {
|
|
||||||
int h = (bytes[i + 1] - '0');
|
|
||||||
int l = (bytes[i + 2] - '0');
|
|
||||||
if (h > 9) {
|
|
||||||
h -= 7;
|
|
||||||
}
|
|
||||||
if (l > 9) {
|
|
||||||
l -= 7;
|
|
||||||
}
|
|
||||||
bytes[length] = (byte) ((h << 4) | l);
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
else if (ch == '+') {
|
|
||||||
bytes[length] = ' ';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bytes[length] = bytes[i];
|
|
||||||
}
|
|
||||||
length++;
|
|
||||||
}
|
|
||||||
return new String(bytes, 0, length, "UTF-8");
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException uee) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the specified date is within today. Returns false otherwise.
|
|
||||||
* @param date
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean isDateToday(Date date) {
|
|
||||||
// TODO But Calendar is so slowwwwwww....
|
|
||||||
Date today = new Date();
|
|
||||||
if (date.getYear() == today.getYear() &&
|
|
||||||
date.getMonth() == today.getMonth() &&
|
|
||||||
date.getDate() == today.getDate()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO disabled this method globally. It is used in all the settings screens but I just
|
|
||||||
* noticed that an unrelated icon was dimmed. Android must share drawables internally.
|
|
||||||
*/
|
|
||||||
public static void setCompoundDrawablesAlpha(TextView view, int alpha) {
|
|
||||||
// Drawable[] drawables = view.getCompoundDrawables();
|
|
||||||
// for (Drawable drawable : drawables) {
|
|
||||||
// if (drawable != null) {
|
|
||||||
// drawable.setAlpha(alpha);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,296 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ListActivity;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.MessagingController;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.activity.setup.AccountSettings;
|
|
||||||
import com.fsck.k9.activity.setup.AccountSetupBasics;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Store;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
|
||||||
|
|
||||||
public class Accounts extends ListActivity implements OnItemClickListener, OnClickListener {
|
|
||||||
private static final int DIALOG_REMOVE_ACCOUNT = 1;
|
|
||||||
/**
|
|
||||||
* Key codes used to open a debug settings screen.
|
|
||||||
*/
|
|
||||||
private static int[] secretKeyCodes = {
|
|
||||||
KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_U,
|
|
||||||
KeyEvent.KEYCODE_G
|
|
||||||
};
|
|
||||||
|
|
||||||
private int mSecretKeyCodeIndex = 0;
|
|
||||||
private Account mSelectedContextAccount;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle icicle) {
|
|
||||||
super.onCreate(icicle);
|
|
||||||
setContentView(R.layout.accounts);
|
|
||||||
ListView listView = getListView();
|
|
||||||
listView.setOnItemClickListener(this);
|
|
||||||
listView.setItemsCanFocus(false);
|
|
||||||
listView.setEmptyView(findViewById(R.id.empty));
|
|
||||||
findViewById(R.id.add_new_account).setOnClickListener(this);
|
|
||||||
registerForContextMenu(listView);
|
|
||||||
|
|
||||||
if (icicle != null && icicle.containsKey("selectedContextAccount")) {
|
|
||||||
mSelectedContextAccount = (Account) icicle.getSerializable("selectedContextAccount");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
if (mSelectedContextAccount != null) {
|
|
||||||
outState.putSerializable("selectedContextAccount", mSelectedContextAccount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
NotificationManager notifMgr = (NotificationManager)
|
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
notifMgr.cancel(1);
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refresh() {
|
|
||||||
Account[] accounts = Preferences.getPreferences(this).getAccounts();
|
|
||||||
getListView().setAdapter(new AccountsAdapter(accounts));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAddNewAccount() {
|
|
||||||
AccountSetupBasics.actionNewAccount(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onEditAccount(Account account) {
|
|
||||||
AccountSettings.actionSettings(this, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRefresh() {
|
|
||||||
MessagingController.getInstance(getApplication()).checkMail(this, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCompose() {
|
|
||||||
Account defaultAccount =
|
|
||||||
Preferences.getPreferences(this).getDefaultAccount();
|
|
||||||
if (defaultAccount != null) {
|
|
||||||
MessageCompose.actionCompose(this, defaultAccount);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
onAddNewAccount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onOpenAccount(Account account) {
|
|
||||||
FolderMessageList.actionHandleAccount(this, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View view) {
|
|
||||||
if (view.getId() == R.id.add_new_account) {
|
|
||||||
onAddNewAccount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDeleteAccount(Account account) {
|
|
||||||
mSelectedContextAccount = account;
|
|
||||||
showDialog(DIALOG_REMOVE_ACCOUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(int id) {
|
|
||||||
switch (id) {
|
|
||||||
case DIALOG_REMOVE_ACCOUNT:
|
|
||||||
return createRemoveAccountDialog();
|
|
||||||
}
|
|
||||||
return super.onCreateDialog(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dialog createRemoveAccountDialog() {
|
|
||||||
return new AlertDialog.Builder(this)
|
|
||||||
.setTitle(R.string.account_delete_dlg_title)
|
|
||||||
.setMessage(getString(R.string.account_delete_dlg_instructions_fmt,
|
|
||||||
mSelectedContextAccount.getDescription()))
|
|
||||||
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
|
||||||
dismissDialog(DIALOG_REMOVE_ACCOUNT);
|
|
||||||
try {
|
|
||||||
((LocalStore)Store.getInstance(
|
|
||||||
mSelectedContextAccount.getLocalStoreUri(),
|
|
||||||
getApplication())).delete();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
mSelectedContextAccount.delete(Preferences.getPreferences(Accounts.this));
|
|
||||||
k9.setServicesEnabled(Accounts.this);
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
|
||||||
dismissDialog(DIALOG_REMOVE_ACCOUNT);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
|
||||||
AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo)item.getMenuInfo();
|
|
||||||
Account account = (Account)getListView().getItemAtPosition(menuInfo.position);
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.delete_account:
|
|
||||||
onDeleteAccount(account);
|
|
||||||
break;
|
|
||||||
case R.id.edit_account:
|
|
||||||
onEditAccount(account);
|
|
||||||
break;
|
|
||||||
case R.id.open:
|
|
||||||
onOpenAccount(account);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onItemClick(AdapterView parent, View view, int position, long id) {
|
|
||||||
Account account = (Account)parent.getItemAtPosition(position);
|
|
||||||
onOpenAccount(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.add_new_account:
|
|
||||||
onAddNewAccount();
|
|
||||||
break;
|
|
||||||
case R.id.check_mail:
|
|
||||||
onRefresh();
|
|
||||||
break;
|
|
||||||
case R.id.compose:
|
|
||||||
onCompose();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
super.onCreateOptionsMenu(menu);
|
|
||||||
getMenuInflater().inflate(R.menu.accounts_option, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
|
||||||
menu.setHeaderTitle(R.string.accounts_context_menu_title);
|
|
||||||
getMenuInflater().inflate(R.menu.accounts_context, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
||||||
if (event.getKeyCode() == secretKeyCodes[mSecretKeyCodeIndex]) {
|
|
||||||
mSecretKeyCodeIndex++;
|
|
||||||
if (mSecretKeyCodeIndex == secretKeyCodes.length) {
|
|
||||||
mSecretKeyCodeIndex = 0;
|
|
||||||
startActivity(new Intent(this, Debug.class));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mSecretKeyCodeIndex = 0;
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
class AccountsAdapter extends ArrayAdapter<Account> {
|
|
||||||
public AccountsAdapter(Account[] accounts) {
|
|
||||||
super(Accounts.this, 0, accounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
Account account = getItem(position);
|
|
||||||
View view;
|
|
||||||
if (convertView != null) {
|
|
||||||
view = convertView;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
view = getLayoutInflater().inflate(R.layout.accounts_item, parent, false);
|
|
||||||
}
|
|
||||||
AccountViewHolder holder = (AccountViewHolder) view.getTag();
|
|
||||||
if (holder == null) {
|
|
||||||
holder = new AccountViewHolder();
|
|
||||||
holder.description = (TextView) view.findViewById(R.id.description);
|
|
||||||
holder.email = (TextView) view.findViewById(R.id.email);
|
|
||||||
holder.newMessageCount = (TextView) view.findViewById(R.id.new_message_count);
|
|
||||||
view.setTag(holder);
|
|
||||||
}
|
|
||||||
holder.description.setText(account.getDescription());
|
|
||||||
holder.email.setText(account.getEmail());
|
|
||||||
if (account.getEmail().equals(account.getDescription())) {
|
|
||||||
holder.email.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
int unreadMessageCount = 0;
|
|
||||||
try {
|
|
||||||
LocalStore localStore = (LocalStore) Store.getInstance(
|
|
||||||
account.getLocalStoreUri(),
|
|
||||||
getApplication());
|
|
||||||
LocalFolder localFolder = (LocalFolder) localStore.getFolder(k9.INBOX);
|
|
||||||
if (localFolder.exists()) {
|
|
||||||
unreadMessageCount = localFolder.getUnreadMessageCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (MessagingException me) {
|
|
||||||
/*
|
|
||||||
* This is not expected to fail under normal circumstances.
|
|
||||||
*/
|
|
||||||
throw new RuntimeException("Unable to get unread count from local store.", me);
|
|
||||||
}
|
|
||||||
holder.newMessageCount.setText(Integer.toString(unreadMessageCount));
|
|
||||||
holder.newMessageCount.setVisibility(unreadMessageCount > 0 ? View.VISIBLE : View.GONE);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AccountViewHolder {
|
|
||||||
public TextView description;
|
|
||||||
public TextView email;
|
|
||||||
public TextView newMessageCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
|
|
||||||
public class Debug extends Activity implements OnCheckedChangeListener {
|
|
||||||
private TextView mVersionView;
|
|
||||||
private CheckBox mEnableDebugLoggingView;
|
|
||||||
private CheckBox mEnableSensitiveLoggingView;
|
|
||||||
|
|
||||||
private Preferences mPreferences;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.debug);
|
|
||||||
|
|
||||||
mPreferences = Preferences.getPreferences(this);
|
|
||||||
|
|
||||||
mVersionView = (TextView)findViewById(R.id.version);
|
|
||||||
mEnableDebugLoggingView = (CheckBox)findViewById(R.id.debug_logging);
|
|
||||||
mEnableSensitiveLoggingView = (CheckBox)findViewById(R.id.sensitive_logging);
|
|
||||||
|
|
||||||
mEnableDebugLoggingView.setOnCheckedChangeListener(this);
|
|
||||||
mEnableSensitiveLoggingView.setOnCheckedChangeListener(this);
|
|
||||||
|
|
||||||
mVersionView.setText(String.format(getString(R.string.debug_version_fmt).toString(),
|
|
||||||
getString(R.string.build_number)));
|
|
||||||
|
|
||||||
mEnableDebugLoggingView.setChecked(k9.DEBUG);
|
|
||||||
mEnableSensitiveLoggingView.setChecked(k9.DEBUG_SENSITIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
|
||||||
if (buttonView.getId() == R.id.debug_logging) {
|
|
||||||
k9.DEBUG = isChecked;
|
|
||||||
mPreferences.setEnableDebugLogging(k9.DEBUG);
|
|
||||||
} else if (buttonView.getId() == R.id.sensitive_logging) {
|
|
||||||
k9.DEBUG_SENSITIVE = isChecked;
|
|
||||||
mPreferences.setEnableSensitiveLogging(k9.DEBUG_SENSITIVE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == R.id.dump_settings) {
|
|
||||||
Preferences.getPreferences(this).dump();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
super.onCreateOptionsMenu(menu);
|
|
||||||
getMenuInflater().inflate(R.menu.debug_option, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,891 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.media.MediaScannerConnection;
|
|
||||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.util.Regex;
|
|
||||||
import android.text.util.Linkify;
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.Window;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.webkit.CacheManager;
|
|
||||||
import android.webkit.UrlInterceptHandler;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.CacheManager.CacheResult;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.MessagingController;
|
|
||||||
import com.fsck.k9.MessagingListener;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
import com.fsck.k9.mail.Address;
|
|
||||||
import com.fsck.k9.mail.Message;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Multipart;
|
|
||||||
import com.fsck.k9.mail.Part;
|
|
||||||
import com.fsck.k9.mail.Message.RecipientType;
|
|
||||||
import com.fsck.k9.mail.internet.MimeHeader;
|
|
||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
|
||||||
import com.fsck.k9.provider.AttachmentProvider;
|
|
||||||
|
|
||||||
public class MessageView extends Activity
|
|
||||||
implements UrlInterceptHandler, OnClickListener {
|
|
||||||
private static final String EXTRA_ACCOUNT = "com.fsck.k9.MessageView_account";
|
|
||||||
private static final String EXTRA_FOLDER = "com.fsck.k9.MessageView_folder";
|
|
||||||
private static final String EXTRA_MESSAGE = "com.fsck.k9.MessageView_message";
|
|
||||||
private static final String EXTRA_FOLDER_UIDS = "com.fsck.k9.MessageView_folderUids";
|
|
||||||
private static final String EXTRA_NEXT = "com.fsck.k9.MessageView_next";
|
|
||||||
|
|
||||||
private TextView mFromView;
|
|
||||||
private TextView mDateView;
|
|
||||||
private TextView mToView;
|
|
||||||
private TextView mSubjectView;
|
|
||||||
private WebView mMessageContentView;
|
|
||||||
private LinearLayout mAttachments;
|
|
||||||
private View mAttachmentIcon;
|
|
||||||
private View mShowPicturesSection;
|
|
||||||
|
|
||||||
private Account mAccount;
|
|
||||||
private String mFolder;
|
|
||||||
private String mMessageUid;
|
|
||||||
private ArrayList<String> mFolderUids;
|
|
||||||
|
|
||||||
private Message mMessage;
|
|
||||||
private String mNextMessageUid = null;
|
|
||||||
private String mPreviousMessageUid = null;
|
|
||||||
|
|
||||||
private DateFormat mDateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
|
|
||||||
private DateFormat mTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
|
|
||||||
|
|
||||||
private Listener mListener = new Listener();
|
|
||||||
private MessageViewHandler mHandler = new MessageViewHandler();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
||||||
switch (keyCode) {
|
|
||||||
case KeyEvent.KEYCODE_DEL: { onDelete(); return true;}
|
|
||||||
case KeyEvent.KEYCODE_F: { onForward(); return true;}
|
|
||||||
case KeyEvent.KEYCODE_A: { onReplyAll(); return true; }
|
|
||||||
case KeyEvent.KEYCODE_R: { onReply(); return true; }
|
|
||||||
case KeyEvent.KEYCODE_J: { onPrevious(); return true; }
|
|
||||||
case KeyEvent.KEYCODE_K: { onNext(); return true; }
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MessageViewHandler extends Handler {
|
|
||||||
private static final int MSG_PROGRESS = 2;
|
|
||||||
private static final int MSG_ADD_ATTACHMENT = 3;
|
|
||||||
private static final int MSG_SET_ATTACHMENTS_ENABLED = 4;
|
|
||||||
private static final int MSG_SET_HEADERS = 5;
|
|
||||||
private static final int MSG_NETWORK_ERROR = 6;
|
|
||||||
private static final int MSG_ATTACHMENT_SAVED = 7;
|
|
||||||
private static final int MSG_ATTACHMENT_NOT_SAVED = 8;
|
|
||||||
private static final int MSG_SHOW_SHOW_PICTURES = 9;
|
|
||||||
private static final int MSG_FETCHING_ATTACHMENT = 10;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessage(android.os.Message msg) {
|
|
||||||
switch (msg.what) {
|
|
||||||
case MSG_PROGRESS:
|
|
||||||
setProgressBarIndeterminateVisibility(msg.arg1 != 0);
|
|
||||||
break;
|
|
||||||
case MSG_ADD_ATTACHMENT:
|
|
||||||
mAttachments.addView((View) msg.obj);
|
|
||||||
mAttachments.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
case MSG_SET_ATTACHMENTS_ENABLED:
|
|
||||||
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
|
||||||
Attachment attachment = (Attachment) mAttachments.getChildAt(i).getTag();
|
|
||||||
attachment.viewButton.setEnabled(msg.arg1 == 1);
|
|
||||||
attachment.downloadButton.setEnabled(msg.arg1 == 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MSG_SET_HEADERS:
|
|
||||||
String[] values = (String[]) msg.obj;
|
|
||||||
setTitle(values[0]);
|
|
||||||
mSubjectView.setText(values[0]);
|
|
||||||
mFromView.setText(values[1]);
|
|
||||||
mDateView.setText(values[2]);
|
|
||||||
mToView.setText(values[3]);
|
|
||||||
mAttachmentIcon.setVisibility(msg.arg1 == 1 ? View.VISIBLE : View.GONE);
|
|
||||||
break;
|
|
||||||
case MSG_NETWORK_ERROR:
|
|
||||||
Toast.makeText(MessageView.this,
|
|
||||||
R.string.status_network_error, Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case MSG_ATTACHMENT_SAVED:
|
|
||||||
Toast.makeText(MessageView.this, String.format(
|
|
||||||
getString(R.string.message_view_status_attachment_saved), msg.obj),
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case MSG_ATTACHMENT_NOT_SAVED:
|
|
||||||
Toast.makeText(MessageView.this,
|
|
||||||
getString(R.string.message_view_status_attachment_not_saved),
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case MSG_SHOW_SHOW_PICTURES:
|
|
||||||
mShowPicturesSection.setVisibility(msg.arg1 == 1 ? View.VISIBLE : View.GONE);
|
|
||||||
break;
|
|
||||||
case MSG_FETCHING_ATTACHMENT:
|
|
||||||
Toast.makeText(MessageView.this,
|
|
||||||
getString(R.string.message_view_fetching_attachment_toast),
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
super.handleMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void progress(boolean progress) {
|
|
||||||
android.os.Message msg = new android.os.Message();
|
|
||||||
msg.what = MSG_PROGRESS;
|
|
||||||
msg.arg1 = progress ? 1 : 0;
|
|
||||||
sendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAttachment(View attachmentView) {
|
|
||||||
android.os.Message msg = new android.os.Message();
|
|
||||||
msg.what = MSG_ADD_ATTACHMENT;
|
|
||||||
msg.obj = attachmentView;
|
|
||||||
sendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAttachmentsEnabled(boolean enabled) {
|
|
||||||
android.os.Message msg = new android.os.Message();
|
|
||||||
msg.what = MSG_SET_ATTACHMENTS_ENABLED;
|
|
||||||
msg.arg1 = enabled ? 1 : 0;
|
|
||||||
sendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeaders(
|
|
||||||
String subject,
|
|
||||||
String from,
|
|
||||||
String date,
|
|
||||||
String to,
|
|
||||||
boolean hasAttachments) {
|
|
||||||
android.os.Message msg = new android.os.Message();
|
|
||||||
msg.what = MSG_SET_HEADERS;
|
|
||||||
msg.arg1 = hasAttachments ? 1 : 0;
|
|
||||||
msg.obj = new String[] { subject, from, date, to };
|
|
||||||
sendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void networkError() {
|
|
||||||
sendEmptyMessage(MSG_NETWORK_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void attachmentSaved(String filename) {
|
|
||||||
android.os.Message msg = new android.os.Message();
|
|
||||||
msg.what = MSG_ATTACHMENT_SAVED;
|
|
||||||
msg.obj = filename;
|
|
||||||
sendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void attachmentNotSaved() {
|
|
||||||
sendEmptyMessage(MSG_ATTACHMENT_NOT_SAVED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fetchingAttachment() {
|
|
||||||
sendEmptyMessage(MSG_FETCHING_ATTACHMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showShowPictures(boolean show) {
|
|
||||||
android.os.Message msg = new android.os.Message();
|
|
||||||
msg.what = MSG_SHOW_SHOW_PICTURES;
|
|
||||||
msg.arg1 = show ? 1 : 0;
|
|
||||||
sendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Attachment {
|
|
||||||
public String name;
|
|
||||||
public String contentType;
|
|
||||||
public long size;
|
|
||||||
public LocalAttachmentBodyPart part;
|
|
||||||
public Button viewButton;
|
|
||||||
public Button downloadButton;
|
|
||||||
public ImageView iconView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void actionView(Context context, Account account,
|
|
||||||
String folder, String messageUid, ArrayList<String> folderUids) {
|
|
||||||
actionView(context, account, folder, messageUid, folderUids, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void actionView(Context context, Account account,
|
|
||||||
String folder, String messageUid, ArrayList<String> folderUids, Bundle extras) {
|
|
||||||
Intent i = new Intent(context, MessageView.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
i.putExtra(EXTRA_FOLDER, folder);
|
|
||||||
i.putExtra(EXTRA_MESSAGE, messageUid);
|
|
||||||
i.putExtra(EXTRA_FOLDER_UIDS, folderUids);
|
|
||||||
if (extras != null) {
|
|
||||||
i.putExtras(extras);
|
|
||||||
}
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreate(Bundle icicle) {
|
|
||||||
super.onCreate(icicle);
|
|
||||||
|
|
||||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
|
||||||
|
|
||||||
setContentView(R.layout.message_view);
|
|
||||||
|
|
||||||
mFromView = (TextView)findViewById(R.id.from);
|
|
||||||
mToView = (TextView)findViewById(R.id.to);
|
|
||||||
mSubjectView = (TextView)findViewById(R.id.subject);
|
|
||||||
mDateView = (TextView)findViewById(R.id.date);
|
|
||||||
mMessageContentView = (WebView)findViewById(R.id.message_content);
|
|
||||||
mAttachments = (LinearLayout)findViewById(R.id.attachments);
|
|
||||||
mAttachmentIcon = findViewById(R.id.attachment);
|
|
||||||
mShowPicturesSection = findViewById(R.id.show_pictures_section);
|
|
||||||
|
|
||||||
mMessageContentView.setVerticalScrollBarEnabled(false);
|
|
||||||
mAttachments.setVisibility(View.GONE);
|
|
||||||
mAttachmentIcon.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
findViewById(R.id.reply).setOnClickListener(this);
|
|
||||||
findViewById(R.id.reply_all).setOnClickListener(this);
|
|
||||||
findViewById(R.id.delete).setOnClickListener(this);
|
|
||||||
findViewById(R.id.show_pictures).setOnClickListener(this);
|
|
||||||
|
|
||||||
// UrlInterceptRegistry.registerHandler(this);
|
|
||||||
|
|
||||||
mMessageContentView.getSettings().setBlockNetworkImage(true);
|
|
||||||
mMessageContentView.getSettings().setSupportZoom(false);
|
|
||||||
|
|
||||||
setTitle("");
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
mAccount = (Account) intent.getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
mFolder = intent.getStringExtra(EXTRA_FOLDER);
|
|
||||||
mMessageUid = intent.getStringExtra(EXTRA_MESSAGE);
|
|
||||||
mFolderUids = intent.getStringArrayListExtra(EXTRA_FOLDER_UIDS);
|
|
||||||
|
|
||||||
View next = findViewById(R.id.next);
|
|
||||||
View previous = findViewById(R.id.previous);
|
|
||||||
/*
|
|
||||||
* Next and Previous Message are not shown in landscape mode, so
|
|
||||||
* we need to check before we use them.
|
|
||||||
*/
|
|
||||||
if (next != null && previous != null) {
|
|
||||||
next.setOnClickListener(this);
|
|
||||||
previous.setOnClickListener(this);
|
|
||||||
|
|
||||||
findSurroundingMessagesUid();
|
|
||||||
|
|
||||||
previous.setVisibility(mPreviousMessageUid != null ? View.VISIBLE : View.GONE);
|
|
||||||
next.setVisibility(mNextMessageUid != null ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
boolean goNext = intent.getBooleanExtra(EXTRA_NEXT, false);
|
|
||||||
if (goNext) {
|
|
||||||
next.requestFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessagingController.getInstance(getApplication()).addListener(mListener);
|
|
||||||
new Thread() {
|
|
||||||
public void run() {
|
|
||||||
// TODO this is a spot that should be eventually handled by a MessagingController
|
|
||||||
// thread pool. We want it in a thread but it can't be blocked by the normal
|
|
||||||
// synchronization stuff in MC.
|
|
||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
||||||
MessagingController.getInstance(getApplication()).loadMessageForView(
|
|
||||||
mAccount,
|
|
||||||
mFolder,
|
|
||||||
mMessageUid,
|
|
||||||
mListener);
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void findSurroundingMessagesUid() {
|
|
||||||
for (int i = 0, count = mFolderUids.size(); i < count; i++) {
|
|
||||||
String messageUid = mFolderUids.get(i);
|
|
||||||
if (messageUid.equals(mMessageUid)) {
|
|
||||||
if (i != 0) {
|
|
||||||
mPreviousMessageUid = mFolderUids.get(i - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i != count - 1) {
|
|
||||||
mNextMessageUid = mFolderUids.get(i + 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
MessagingController.getInstance(getApplication()).addListener(mListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
MessagingController.getInstance(getApplication()).removeListener(mListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDelete() {
|
|
||||||
if (mMessage != null) {
|
|
||||||
MessagingController.getInstance(getApplication()).deleteMessage(
|
|
||||||
mAccount,
|
|
||||||
mFolder,
|
|
||||||
mMessage,
|
|
||||||
null);
|
|
||||||
Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show();
|
|
||||||
|
|
||||||
// Remove this message's Uid locally
|
|
||||||
mFolderUids.remove(mMessage.getUid());
|
|
||||||
// Check if we have previous/next messages available before choosing
|
|
||||||
// which one to display
|
|
||||||
findSurroundingMessagesUid();
|
|
||||||
|
|
||||||
if (mPreviousMessageUid != null) {
|
|
||||||
onPrevious();
|
|
||||||
} else if (mNextMessageUid != null) {
|
|
||||||
onNext();
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onReply() {
|
|
||||||
if (mMessage != null) {
|
|
||||||
MessageCompose.actionReply(this, mAccount, mMessage, false);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onReplyAll() {
|
|
||||||
if (mMessage != null) {
|
|
||||||
MessageCompose.actionReply(this, mAccount, mMessage, true);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onForward() {
|
|
||||||
if (mMessage != null) {
|
|
||||||
MessageCompose.actionForward(this, mAccount, mMessage);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNext() {
|
|
||||||
Bundle extras = new Bundle(1);
|
|
||||||
extras.putBoolean(EXTRA_NEXT, true);
|
|
||||||
MessageView.actionView(this, mAccount, mFolder, mNextMessageUid, mFolderUids, extras);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPrevious() {
|
|
||||||
MessageView.actionView(this, mAccount, mFolder, mPreviousMessageUid, mFolderUids);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMarkAsUnread() {
|
|
||||||
MessagingController.getInstance(getApplication()).markMessageRead(
|
|
||||||
mAccount,
|
|
||||||
mFolder,
|
|
||||||
mMessage.getUid(),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a unique file in the given directory by appending a hyphen
|
|
||||||
* and a number to the given filename.
|
|
||||||
* @param directory
|
|
||||||
* @param filename
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private File createUniqueFile(File directory, String filename) {
|
|
||||||
File file = new File(directory, filename);
|
|
||||||
if (!file.exists()) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
// Get the extension of the file, if any.
|
|
||||||
int index = filename.lastIndexOf('.');
|
|
||||||
String format;
|
|
||||||
if (index != -1) {
|
|
||||||
String name = filename.substring(0, index);
|
|
||||||
String extension = filename.substring(index);
|
|
||||||
format = name + "-%d" + extension;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
format = filename + "-%d";
|
|
||||||
}
|
|
||||||
for (int i = 2; i < Integer.MAX_VALUE; i++) {
|
|
||||||
file = new File(directory, String.format(format, i));
|
|
||||||
if (!file.exists()) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDownloadAttachment(Attachment attachment) {
|
|
||||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
||||||
/*
|
|
||||||
* Abort early if there's no place to save the attachment. We don't want to spend
|
|
||||||
* the time downloading it and then abort.
|
|
||||||
*/
|
|
||||||
Toast.makeText(this,
|
|
||||||
getString(R.string.message_view_status_attachment_not_saved),
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MessagingController.getInstance(getApplication()).loadAttachment(
|
|
||||||
mAccount,
|
|
||||||
mMessage,
|
|
||||||
attachment.part,
|
|
||||||
new Object[] { true, attachment },
|
|
||||||
mListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onViewAttachment(Attachment attachment) {
|
|
||||||
MessagingController.getInstance(getApplication()).loadAttachment(
|
|
||||||
mAccount,
|
|
||||||
mMessage,
|
|
||||||
attachment.part,
|
|
||||||
new Object[] { false, attachment },
|
|
||||||
mListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onShowPictures() {
|
|
||||||
mMessageContentView.getSettings().setBlockNetworkImage(false);
|
|
||||||
mShowPicturesSection.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View view) {
|
|
||||||
switch (view.getId()) {
|
|
||||||
case R.id.reply:
|
|
||||||
onReply();
|
|
||||||
break;
|
|
||||||
case R.id.reply_all:
|
|
||||||
onReplyAll();
|
|
||||||
break;
|
|
||||||
case R.id.delete:
|
|
||||||
onDelete();
|
|
||||||
break;
|
|
||||||
case R.id.next:
|
|
||||||
onNext();
|
|
||||||
break;
|
|
||||||
case R.id.previous:
|
|
||||||
onPrevious();
|
|
||||||
break;
|
|
||||||
case R.id.download:
|
|
||||||
onDownloadAttachment((Attachment) view.getTag());
|
|
||||||
break;
|
|
||||||
case R.id.view:
|
|
||||||
onViewAttachment((Attachment) view.getTag());
|
|
||||||
break;
|
|
||||||
case R.id.show_pictures:
|
|
||||||
onShowPictures();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.delete:
|
|
||||||
onDelete();
|
|
||||||
break;
|
|
||||||
case R.id.reply:
|
|
||||||
onReply();
|
|
||||||
break;
|
|
||||||
case R.id.reply_all:
|
|
||||||
onReplyAll();
|
|
||||||
break;
|
|
||||||
case R.id.forward:
|
|
||||||
onForward();
|
|
||||||
break;
|
|
||||||
case R.id.mark_as_unread:
|
|
||||||
onMarkAsUnread();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
super.onCreateOptionsMenu(menu);
|
|
||||||
getMenuInflater().inflate(R.menu.message_view_option, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheResult service(String url, Map<String, String> headers) {
|
|
||||||
String prefix = "http://cid/";
|
|
||||||
if (url.startsWith(prefix)) {
|
|
||||||
try {
|
|
||||||
String contentId = url.substring(prefix.length());
|
|
||||||
final Part part = MimeUtility.findPartByContentId(mMessage, "<" + contentId + ">");
|
|
||||||
if (part != null) {
|
|
||||||
CacheResult cr = new CacheManager.CacheResult();
|
|
||||||
// TODO looks fixed in Mainline, cr.setInputStream
|
|
||||||
// part.getBody().writeTo(cr.getStream());
|
|
||||||
return cr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap getPreviewIcon(Attachment attachment) throws MessagingException {
|
|
||||||
try {
|
|
||||||
return BitmapFactory.decodeStream(
|
|
||||||
getContentResolver().openInputStream(
|
|
||||||
AttachmentProvider.getAttachmentThumbnailUri(mAccount,
|
|
||||||
attachment.part.getAttachmentId(),
|
|
||||||
62,
|
|
||||||
62)));
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
/*
|
|
||||||
* We don't care what happened, we just return null for the preview icon.
|
|
||||||
*/
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Formats the given size as a String in bytes, kB, MB or GB with a single digit
|
|
||||||
* of precision. Ex: 12,315,000 = 12.3 MB
|
|
||||||
*/
|
|
||||||
public static String formatSize(float size) {
|
|
||||||
long kb = 1024;
|
|
||||||
long mb = (kb * 1024);
|
|
||||||
long gb = (mb * 1024);
|
|
||||||
if (size < kb) {
|
|
||||||
return String.format("%d bytes", (int) size);
|
|
||||||
}
|
|
||||||
else if (size < mb) {
|
|
||||||
return String.format("%.1f kB", size / kb);
|
|
||||||
}
|
|
||||||
else if (size < gb) {
|
|
||||||
return String.format("%.1f MB", size / mb);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return String.format("%.1f GB", size / gb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderAttachments(Part part, int depth) throws MessagingException {
|
|
||||||
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
|
|
||||||
String name = MimeUtility.getHeaderParameter(contentType, "name");
|
|
||||||
if (name != null) {
|
|
||||||
/*
|
|
||||||
* We're guaranteed size because LocalStore.fetch puts it there.
|
|
||||||
*/
|
|
||||||
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
|
|
||||||
int size = Integer.parseInt(MimeUtility.getHeaderParameter(contentDisposition, "size"));
|
|
||||||
|
|
||||||
Attachment attachment = new Attachment();
|
|
||||||
attachment.size = size;
|
|
||||||
attachment.contentType = part.getMimeType();
|
|
||||||
attachment.name = name;
|
|
||||||
attachment.part = (LocalAttachmentBodyPart) part;
|
|
||||||
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
View view = inflater.inflate(R.layout.message_view_attachment, null);
|
|
||||||
|
|
||||||
TextView attachmentName = (TextView)view.findViewById(R.id.attachment_name);
|
|
||||||
TextView attachmentInfo = (TextView)view.findViewById(R.id.attachment_info);
|
|
||||||
ImageView attachmentIcon = (ImageView)view.findViewById(R.id.attachment_icon);
|
|
||||||
Button attachmentView = (Button)view.findViewById(R.id.view);
|
|
||||||
Button attachmentDownload = (Button)view.findViewById(R.id.download);
|
|
||||||
|
|
||||||
if ((!MimeUtility.mimeTypeMatches(attachment.contentType,
|
|
||||||
k9.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
|
|
||||||
|| (MimeUtility.mimeTypeMatches(attachment.contentType,
|
|
||||||
k9.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
|
|
||||||
attachmentView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
if ((!MimeUtility.mimeTypeMatches(attachment.contentType,
|
|
||||||
k9.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))
|
|
||||||
|| (MimeUtility.mimeTypeMatches(attachment.contentType,
|
|
||||||
k9.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) {
|
|
||||||
attachmentDownload.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.size > k9.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
|
|
||||||
attachmentView.setVisibility(View.GONE);
|
|
||||||
attachmentDownload.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachment.viewButton = attachmentView;
|
|
||||||
attachment.downloadButton = attachmentDownload;
|
|
||||||
attachment.iconView = attachmentIcon;
|
|
||||||
|
|
||||||
view.setTag(attachment);
|
|
||||||
attachmentView.setOnClickListener(this);
|
|
||||||
attachmentView.setTag(attachment);
|
|
||||||
attachmentDownload.setOnClickListener(this);
|
|
||||||
attachmentDownload.setTag(attachment);
|
|
||||||
|
|
||||||
attachmentName.setText(name);
|
|
||||||
attachmentInfo.setText(formatSize(size));
|
|
||||||
|
|
||||||
Bitmap previewIcon = getPreviewIcon(attachment);
|
|
||||||
if (previewIcon != null) {
|
|
||||||
attachmentIcon.setImageBitmap(previewIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
mHandler.addAttachment(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.getBody() instanceof Multipart) {
|
|
||||||
Multipart mp = (Multipart)part.getBody();
|
|
||||||
for (int i = 0; i < mp.getCount(); i++) {
|
|
||||||
renderAttachments(mp.getBodyPart(i), depth + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Listener extends MessagingListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadMessageForViewHeadersAvailable(Account account, String folder, String uid,
|
|
||||||
final Message message) {
|
|
||||||
MessageView.this.mMessage = message;
|
|
||||||
try {
|
|
||||||
String subjectText = message.getSubject();
|
|
||||||
String fromText = Address.toFriendly(message.getFrom());
|
|
||||||
String dateText = Utility.isDateToday(message.getSentDate()) ?
|
|
||||||
mTimeFormat.format(message.getSentDate()) :
|
|
||||||
mDateTimeFormat.format(message.getSentDate());
|
|
||||||
String toText = Address.toFriendly(message.getRecipients(RecipientType.TO));
|
|
||||||
boolean hasAttachments = ((LocalMessage) message).getAttachmentCount() > 0;
|
|
||||||
mHandler.setHeaders(subjectText,
|
|
||||||
fromText,
|
|
||||||
dateText,
|
|
||||||
toText,
|
|
||||||
hasAttachments);
|
|
||||||
}
|
|
||||||
catch (MessagingException me) {
|
|
||||||
if (Config.LOGV) {
|
|
||||||
Log.v(k9.LOG_TAG, "loadMessageForViewHeadersAvailable", me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadMessageForViewBodyAvailable(Account account, String folder, String uid,
|
|
||||||
Message message) {
|
|
||||||
SpannableString markup;
|
|
||||||
MessageView.this.mMessage = message;
|
|
||||||
try {
|
|
||||||
Part part = MimeUtility.findFirstPartByMimeType(mMessage, "text/html");
|
|
||||||
if (part == null) {
|
|
||||||
part = MimeUtility.findFirstPartByMimeType(mMessage, "text/plain");
|
|
||||||
}
|
|
||||||
if (part != null) {
|
|
||||||
String text = MimeUtility.getTextFromPart(part);
|
|
||||||
if (part.getMimeType().equalsIgnoreCase("text/html")) {
|
|
||||||
text = text.replaceAll("cid:", "http://cid/");
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* Convert plain text to HTML by replacing
|
|
||||||
* \r?\n with <br> and adding a html/body wrapper.
|
|
||||||
*/
|
|
||||||
text = text.replaceAll("\r?\n", "<br>");
|
|
||||||
text = "<html><body>" + text + "</body></html>";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO this should be smarter, change to regex for img, but consider how to
|
|
||||||
* get backgroung images and a million other things that HTML allows.
|
|
||||||
*/
|
|
||||||
if (text.contains("<img")) {
|
|
||||||
mHandler.showShowPictures(true);
|
|
||||||
}
|
|
||||||
markup = new SpannableString(text);
|
|
||||||
Linkify.addLinks(markup, Linkify.ALL);
|
|
||||||
|
|
||||||
mMessageContentView.loadDataWithBaseURL("email://", markup.toString(), "text/html",
|
|
||||||
"utf-8", null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mMessageContentView.loadUrl("file:///android_asset/empty.html");
|
|
||||||
}
|
|
||||||
renderAttachments(mMessage, 0);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
if (Config.LOGV) {
|
|
||||||
Log.v(k9.LOG_TAG, "loadMessageForViewBodyAvailable", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadMessageForViewFailed(Account account, String folder, String uid,
|
|
||||||
final String message) {
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
setProgressBarIndeterminateVisibility(false);
|
|
||||||
mHandler.networkError();
|
|
||||||
mMessageContentView.loadUrl("file:///android_asset/empty.html");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadMessageForViewFinished(Account account, String folder, String uid,
|
|
||||||
Message message) {
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
setProgressBarIndeterminateVisibility(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadMessageForViewStarted(Account account, String folder, String uid) {
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
mMessageContentView.loadUrl("file:///android_asset/loading.html");
|
|
||||||
setProgressBarIndeterminateVisibility(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadAttachmentStarted(Account account, Message message,
|
|
||||||
Part part, Object tag, boolean requiresDownload) {
|
|
||||||
mHandler.setAttachmentsEnabled(false);
|
|
||||||
mHandler.progress(true);
|
|
||||||
if (requiresDownload) {
|
|
||||||
mHandler.fetchingAttachment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadAttachmentFinished(Account account, Message message,
|
|
||||||
Part part, Object tag) {
|
|
||||||
mHandler.setAttachmentsEnabled(true);
|
|
||||||
mHandler.progress(false);
|
|
||||||
|
|
||||||
Object[] params = (Object[]) tag;
|
|
||||||
boolean download = (Boolean) params[0];
|
|
||||||
Attachment attachment = (Attachment) params[1];
|
|
||||||
|
|
||||||
if (download) {
|
|
||||||
try {
|
|
||||||
File file = createUniqueFile(Environment.getExternalStorageDirectory(),
|
|
||||||
attachment.name);
|
|
||||||
Uri uri = AttachmentProvider.getAttachmentUri(
|
|
||||||
mAccount,
|
|
||||||
attachment.part.getAttachmentId());
|
|
||||||
InputStream in = getContentResolver().openInputStream(uri);
|
|
||||||
OutputStream out = new FileOutputStream(file);
|
|
||||||
IOUtils.copy(in, out);
|
|
||||||
out.flush();
|
|
||||||
out.close();
|
|
||||||
in.close();
|
|
||||||
mHandler.attachmentSaved(file.getName());
|
|
||||||
new MediaScannerNotifier(MessageView.this, file);
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
mHandler.attachmentNotSaved();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Uri uri = AttachmentProvider.getAttachmentUri(
|
|
||||||
mAccount,
|
|
||||||
attachment.part.getAttachmentId());
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(uri);
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadAttachmentFailed(Account account, Message message, Part part,
|
|
||||||
Object tag, String reason) {
|
|
||||||
mHandler.setAttachmentsEnabled(true);
|
|
||||||
mHandler.progress(false);
|
|
||||||
mHandler.networkError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MediaScannerNotifier implements MediaScannerConnectionClient {
|
|
||||||
private MediaScannerConnection mConnection;
|
|
||||||
private File mFile;
|
|
||||||
|
|
||||||
public MediaScannerNotifier(Context context, File file) {
|
|
||||||
mFile = file;
|
|
||||||
mConnection = new MediaScannerConnection(context, this);
|
|
||||||
mConnection.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMediaScannerConnected() {
|
|
||||||
mConnection.scanFile(mFile.getAbsolutePath(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onScanCompleted(String path, Uri uri) {
|
|
||||||
try {
|
|
||||||
if (uri != null) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(uri);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mConnection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener that the user can register for global, persistent progress events.
|
|
||||||
*/
|
|
||||||
public interface ProgressListener {
|
|
||||||
/**
|
|
||||||
* @param context
|
|
||||||
* @param title
|
|
||||||
* @param message
|
|
||||||
* @param currentProgress
|
|
||||||
* @param maxProgress
|
|
||||||
* @param indeterminate
|
|
||||||
*/
|
|
||||||
void showProgress(Context context, String title, String message, long currentProgress,
|
|
||||||
long maxProgress, boolean indeterminate);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context
|
|
||||||
* @param title
|
|
||||||
* @param message
|
|
||||||
* @param currentProgress
|
|
||||||
* @param maxProgress
|
|
||||||
* @param indeterminate
|
|
||||||
*/
|
|
||||||
void updateProgress(Context context, String title, String message, long currentProgress,
|
|
||||||
long maxProgress, boolean indeterminate);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context
|
|
||||||
*/
|
|
||||||
void hideProgress(Context context);
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Welcome activity initializes the application and decides what Activity
|
|
||||||
* the user should start with.
|
|
||||||
* If no accounts are configured the user is taken to the Accounts Activity where they
|
|
||||||
* can configure an account.
|
|
||||||
* If a single account is configured the user is taken directly to the FolderMessageList for
|
|
||||||
* the INBOX of that account.
|
|
||||||
* If more than one account is configuref the user is takaen to the Accounts Activity so they
|
|
||||||
* can select an account.
|
|
||||||
*/
|
|
||||||
public class Welcome extends Activity {
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle icicle) {
|
|
||||||
super.onCreate(icicle);
|
|
||||||
|
|
||||||
Account[] accounts = Preferences.getPreferences(this).getAccounts();
|
|
||||||
if (accounts.length == 1) {
|
|
||||||
FolderMessageList.actionHandleAccount(this, accounts[0], k9.INBOX);
|
|
||||||
} else {
|
|
||||||
startActivity(new Intent(this, Accounts.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,170 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.preference.PreferenceActivity;
|
|
||||||
import android.preference.EditTextPreference;
|
|
||||||
import android.preference.ListPreference;
|
|
||||||
import android.preference.CheckBoxPreference;
|
|
||||||
import android.preference.Preference;
|
|
||||||
import android.preference.RingtonePreference;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
|
|
||||||
public class AccountSettings extends PreferenceActivity {
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
|
|
||||||
private static final String PREFERENCE_TOP_CATERGORY = "account_settings";
|
|
||||||
private static final String PREFERENCE_DESCRIPTION = "account_description";
|
|
||||||
private static final String PREFERENCE_COMPOSITION = "composition";
|
|
||||||
private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
|
|
||||||
private static final String PREFERENCE_DEFAULT = "account_default";
|
|
||||||
private static final String PREFERENCE_NOTIFY = "account_notify";
|
|
||||||
private static final String PREFERENCE_VIBRATE = "account_vibrate";
|
|
||||||
private static final String PREFERENCE_RINGTONE = "account_ringtone";
|
|
||||||
private static final String PREFERENCE_INCOMING = "incoming";
|
|
||||||
private static final String PREFERENCE_OUTGOING = "outgoing";
|
|
||||||
|
|
||||||
private Account mAccount;
|
|
||||||
|
|
||||||
private EditTextPreference mAccountDescription;
|
|
||||||
private ListPreference mCheckFrequency;
|
|
||||||
private CheckBoxPreference mAccountDefault;
|
|
||||||
private CheckBoxPreference mAccountNotify;
|
|
||||||
private CheckBoxPreference mAccountVibrate;
|
|
||||||
private RingtonePreference mAccountRingtone;
|
|
||||||
|
|
||||||
public static void actionSettings(Context context, Account account) {
|
|
||||||
Intent i = new Intent(context, AccountSettings.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
|
|
||||||
addPreferencesFromResource(R.xml.account_settings_preferences);
|
|
||||||
|
|
||||||
Preference category = findPreference(PREFERENCE_TOP_CATERGORY);
|
|
||||||
category.setTitle(getString(R.string.account_settings_title_fmt));
|
|
||||||
|
|
||||||
mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
|
|
||||||
mAccountDescription.setSummary(mAccount.getDescription());
|
|
||||||
mAccountDescription.setText(mAccount.getDescription());
|
|
||||||
mAccountDescription.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
final String summary = newValue.toString();
|
|
||||||
mAccountDescription.setSummary(summary);
|
|
||||||
mAccountDescription.setText(summary);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
|
|
||||||
mCheckFrequency.setValue(String.valueOf(mAccount.getAutomaticCheckIntervalMinutes()));
|
|
||||||
mCheckFrequency.setSummary(mCheckFrequency.getEntry());
|
|
||||||
mCheckFrequency.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
final String summary = newValue.toString();
|
|
||||||
int index = mCheckFrequency.findIndexOfValue(summary);
|
|
||||||
mCheckFrequency.setSummary(mCheckFrequency.getEntries()[index]);
|
|
||||||
mCheckFrequency.setValue(summary);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mAccountDefault = (CheckBoxPreference) findPreference(PREFERENCE_DEFAULT);
|
|
||||||
mAccountDefault.setChecked(
|
|
||||||
mAccount.equals(Preferences.getPreferences(this).getDefaultAccount()));
|
|
||||||
|
|
||||||
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
|
|
||||||
mAccountNotify.setChecked(mAccount.isNotifyNewMail());
|
|
||||||
|
|
||||||
mAccountRingtone = (RingtonePreference) findPreference(PREFERENCE_RINGTONE);
|
|
||||||
|
|
||||||
// XXX: The following two lines act as a workaround for the RingtonePreference
|
|
||||||
// which does not let us set/get the value programmatically
|
|
||||||
SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
|
|
||||||
prefs.edit().putString(PREFERENCE_RINGTONE, mAccount.getRingtone()).commit();
|
|
||||||
|
|
||||||
mAccountVibrate = (CheckBoxPreference) findPreference(PREFERENCE_VIBRATE);
|
|
||||||
mAccountVibrate.setChecked(mAccount.isVibrate());
|
|
||||||
|
|
||||||
|
|
||||||
findPreference(PREFERENCE_COMPOSITION).setOnPreferenceClickListener(
|
|
||||||
new Preference.OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
onCompositionSettings();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener(
|
|
||||||
new Preference.OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
onIncomingSettings();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference(PREFERENCE_OUTGOING).setOnPreferenceClickListener(
|
|
||||||
new Preference.OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
onOutgoingSettings();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
mAccount.refresh(Preferences.getPreferences(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveSettings() {
|
|
||||||
if (mAccountDefault.isChecked()) {
|
|
||||||
Preferences.getPreferences(this).setDefaultAccount(mAccount);
|
|
||||||
}
|
|
||||||
mAccount.setDescription(mAccountDescription.getText());
|
|
||||||
mAccount.setNotifyNewMail(mAccountNotify.isChecked());
|
|
||||||
mAccount.setAutomaticCheckIntervalMinutes(Integer.parseInt(mCheckFrequency.getValue()));
|
|
||||||
mAccount.setVibrate(mAccountVibrate.isChecked());
|
|
||||||
SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
|
|
||||||
mAccount.setRingtone(prefs.getString(PREFERENCE_RINGTONE, null));
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
k9.setServicesEnabled(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCompositionSettings() {
|
|
||||||
AccountSetupComposition.actionEditCompositionSettings(this, mAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onIncomingSettings() {
|
|
||||||
AccountSetupIncoming.actionEditIncomingSettings(this, mAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onOutgoingSettings() {
|
|
||||||
AccountSetupOutgoing.actionEditOutgoingSettings(this, mAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompts the user to select an account type. The account type, along with the
|
|
||||||
* passed in email address, password and makeDefault are then passed on to the
|
|
||||||
* AccountSetupIncoming activity.
|
|
||||||
*/
|
|
||||||
public class AccountSetupAccountType extends Activity implements OnClickListener {
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
|
|
||||||
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
|
|
||||||
|
|
||||||
private Account mAccount;
|
|
||||||
|
|
||||||
private boolean mMakeDefault;
|
|
||||||
|
|
||||||
public static void actionSelectAccountType(Context context, Account account, boolean makeDefault) {
|
|
||||||
Intent i = new Intent(context, AccountSetupAccountType.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.account_setup_account_type);
|
|
||||||
((Button)findViewById(R.id.pop)).setOnClickListener(this);
|
|
||||||
((Button)findViewById(R.id.imap)).setOnClickListener(this);
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
mMakeDefault = (boolean)getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPop() {
|
|
||||||
try {
|
|
||||||
URI uri = new URI(mAccount.getStoreUri());
|
|
||||||
uri = new URI("pop3", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null);
|
|
||||||
mAccount.setStoreUri(uri.toString());
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* This should not happen.
|
|
||||||
*/
|
|
||||||
throw new Error(use);
|
|
||||||
}
|
|
||||||
AccountSetupIncoming.actionIncomingSettings(this, mAccount, mMakeDefault);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onImap() {
|
|
||||||
try {
|
|
||||||
URI uri = new URI(mAccount.getStoreUri());
|
|
||||||
uri = new URI("imap", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null);
|
|
||||||
mAccount.setStoreUri(uri.toString());
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* This should not happen.
|
|
||||||
*/
|
|
||||||
throw new Error(use);
|
|
||||||
}
|
|
||||||
AccountSetupIncoming.actionIncomingSettings(this, mAccount, mMakeDefault);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
switch (v.getId()) {
|
|
||||||
case R.id.pop:
|
|
||||||
onPop();
|
|
||||||
break;
|
|
||||||
case R.id.imap:
|
|
||||||
onImap();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,388 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.XmlResourceParser;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.Contacts;
|
|
||||||
import android.provider.Contacts.People.ContactMethods;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.EmailAddressValidator;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompts the user for the email address and password. Also prompts for
|
|
||||||
* "Use this account as default" if this is the 2nd+ account being set up.
|
|
||||||
* Attempts to lookup default settings for the domain the user specified. If the
|
|
||||||
* domain is known the settings are handed off to the AccountSetupCheckSettings
|
|
||||||
* activity. If no settings are found the settings are handed off to the
|
|
||||||
* AccountSetupAccountType activity.
|
|
||||||
*/
|
|
||||||
public class AccountSetupBasics extends Activity
|
|
||||||
implements OnClickListener, TextWatcher {
|
|
||||||
private final static String EXTRA_ACCOUNT = "com.fsck.k9.AccountSetupBasics.account";
|
|
||||||
private final static int DIALOG_NOTE = 1;
|
|
||||||
private final static String STATE_KEY_PROVIDER =
|
|
||||||
"com.fsck.k9.AccountSetupBasics.provider";
|
|
||||||
|
|
||||||
private Preferences mPrefs;
|
|
||||||
private EditText mEmailView;
|
|
||||||
private EditText mPasswordView;
|
|
||||||
private CheckBox mDefaultView;
|
|
||||||
private Button mNextButton;
|
|
||||||
private Button mManualSetupButton;
|
|
||||||
private Account mAccount;
|
|
||||||
private Provider mProvider;
|
|
||||||
|
|
||||||
private EmailAddressValidator mEmailValidator = new EmailAddressValidator();
|
|
||||||
|
|
||||||
public static void actionNewAccount(Context context) {
|
|
||||||
Intent i = new Intent(context, AccountSetupBasics.class);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.account_setup_basics);
|
|
||||||
mPrefs = Preferences.getPreferences(this);
|
|
||||||
mEmailView = (EditText)findViewById(R.id.account_email);
|
|
||||||
mPasswordView = (EditText)findViewById(R.id.account_password);
|
|
||||||
mDefaultView = (CheckBox)findViewById(R.id.account_default);
|
|
||||||
mNextButton = (Button)findViewById(R.id.next);
|
|
||||||
mManualSetupButton = (Button)findViewById(R.id.manual_setup);
|
|
||||||
|
|
||||||
mNextButton.setOnClickListener(this);
|
|
||||||
mManualSetupButton.setOnClickListener(this);
|
|
||||||
|
|
||||||
mEmailView.addTextChangedListener(this);
|
|
||||||
mPasswordView.addTextChangedListener(this);
|
|
||||||
|
|
||||||
if (mPrefs.getAccounts().length > 0) {
|
|
||||||
mDefaultView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
|
|
||||||
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
|
|
||||||
mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
|
|
||||||
if (mProvider != null) {
|
|
||||||
outState.putSerializable(STATE_KEY_PROVIDER, mProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateFields() {
|
|
||||||
boolean valid = Utility.requiredFieldValid(mEmailView)
|
|
||||||
&& Utility.requiredFieldValid(mPasswordView)
|
|
||||||
&& mEmailValidator.isValid(mEmailView.getText().toString());
|
|
||||||
mNextButton.setEnabled(valid);
|
|
||||||
mManualSetupButton.setEnabled(valid);
|
|
||||||
/*
|
|
||||||
* Dim the next button's icon to 50% if the button is disabled.
|
|
||||||
* TODO this can probably be done with a stateful drawable. Check into it.
|
|
||||||
* android:state_enabled
|
|
||||||
*/
|
|
||||||
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getOwnerName() {
|
|
||||||
String name = null;
|
|
||||||
String projection[] = {
|
|
||||||
ContactMethods.NAME
|
|
||||||
};
|
|
||||||
Cursor c = getContentResolver().query(
|
|
||||||
Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"), projection, null, null,
|
|
||||||
null);
|
|
||||||
if (c.getCount() > 0) {
|
|
||||||
c.moveToFirst();
|
|
||||||
name = c.getString(0);
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name == null || name.length() == 0) {
|
|
||||||
Account account = Preferences.getPreferences(this).getDefaultAccount();
|
|
||||||
if (account != null) {
|
|
||||||
name = account.getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(int id) {
|
|
||||||
if (id == DIALOG_NOTE) {
|
|
||||||
if (mProvider != null && mProvider.note != null) {
|
|
||||||
return new AlertDialog.Builder(this)
|
|
||||||
.setMessage(mProvider.note)
|
|
||||||
.setPositiveButton(
|
|
||||||
getString(R.string.okay_action),
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
finishAutoSetup();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(
|
|
||||||
getString(R.string.cancel_action),
|
|
||||||
null)
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finishAutoSetup() {
|
|
||||||
String email = mEmailView.getText().toString();
|
|
||||||
String password = mPasswordView.getText().toString();
|
|
||||||
String[] emailParts = email.split("@");
|
|
||||||
String user = emailParts[0];
|
|
||||||
String domain = emailParts[1];
|
|
||||||
URI incomingUri = null;
|
|
||||||
URI outgoingUri = null;
|
|
||||||
try {
|
|
||||||
String incomingUsername = mProvider.incomingUsernameTemplate;
|
|
||||||
incomingUsername = incomingUsername.replaceAll("\\$email", email);
|
|
||||||
incomingUsername = incomingUsername.replaceAll("\\$user", user);
|
|
||||||
incomingUsername = incomingUsername.replaceAll("\\$domain", domain);
|
|
||||||
|
|
||||||
URI incomingUriTemplate = mProvider.incomingUriTemplate;
|
|
||||||
incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":"
|
|
||||||
+ password, incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), null,
|
|
||||||
null, null);
|
|
||||||
|
|
||||||
String outgoingUsername = mProvider.outgoingUsernameTemplate;
|
|
||||||
outgoingUsername = outgoingUsername.replaceAll("\\$email", email);
|
|
||||||
outgoingUsername = outgoingUsername.replaceAll("\\$user", user);
|
|
||||||
outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain);
|
|
||||||
|
|
||||||
URI outgoingUriTemplate = mProvider.outgoingUriTemplate;
|
|
||||||
outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
|
|
||||||
+ password, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
|
|
||||||
null, null);
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* If there is some problem with the URI we give up and go on to
|
|
||||||
* manual setup.
|
|
||||||
*/
|
|
||||||
onManualSetup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mAccount = new Account(this);
|
|
||||||
mAccount.setName(getOwnerName());
|
|
||||||
mAccount.setEmail(email);
|
|
||||||
mAccount.setStoreUri(incomingUri.toString());
|
|
||||||
mAccount.setTransportUri(outgoingUri.toString());
|
|
||||||
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
|
|
||||||
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
|
|
||||||
mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
|
|
||||||
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
|
|
||||||
if (incomingUri.toString().startsWith("imap")) {
|
|
||||||
mAccount.setDeletePolicy(Account.DELETE_POLICY_ON_DELETE);
|
|
||||||
}
|
|
||||||
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNext() {
|
|
||||||
String email = mEmailView.getText().toString();
|
|
||||||
String password = mPasswordView.getText().toString();
|
|
||||||
String[] emailParts = email.split("@");
|
|
||||||
String user = emailParts[0];
|
|
||||||
String domain = emailParts[1];
|
|
||||||
mProvider = findProviderForDomain(domain);
|
|
||||||
if (mProvider == null) {
|
|
||||||
/*
|
|
||||||
* We don't have default settings for this account, start the manual
|
|
||||||
* setup process.
|
|
||||||
*/
|
|
||||||
onManualSetup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mProvider.note != null) {
|
|
||||||
showDialog(DIALOG_NOTE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
finishAutoSetup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
mAccount.setDescription(mAccount.getEmail());
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
if (mDefaultView.isChecked()) {
|
|
||||||
Preferences.getPreferences(this).setDefaultAccount(mAccount);
|
|
||||||
}
|
|
||||||
k9.setServicesEnabled(this);
|
|
||||||
AccountSetupNames.actionSetNames(this, mAccount);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onManualSetup() {
|
|
||||||
String email = mEmailView.getText().toString();
|
|
||||||
String password = mPasswordView.getText().toString();
|
|
||||||
String[] emailParts = email.split("@");
|
|
||||||
String user = emailParts[0];
|
|
||||||
String domain = emailParts[1];
|
|
||||||
|
|
||||||
mAccount = new Account(this);
|
|
||||||
mAccount.setName(getOwnerName());
|
|
||||||
mAccount.setEmail(email);
|
|
||||||
try {
|
|
||||||
URI uri = new URI("placeholder", user + ":" + password, "mail." + domain, -1, null,
|
|
||||||
null, null);
|
|
||||||
mAccount.setStoreUri(uri.toString());
|
|
||||||
mAccount.setTransportUri(uri.toString());
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* If we can't set up the URL we just continue. It's only for
|
|
||||||
* convenience.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
|
|
||||||
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
|
|
||||||
mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
|
|
||||||
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
|
|
||||||
|
|
||||||
AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked());
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
switch (v.getId()) {
|
|
||||||
case R.id.next:
|
|
||||||
onNext();
|
|
||||||
break;
|
|
||||||
case R.id.manual_setup:
|
|
||||||
onManualSetup();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to get the given attribute as a String resource first, and if it fails
|
|
||||||
* returns the attribute as a simple String value.
|
|
||||||
* @param xml
|
|
||||||
* @param name
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private String getXmlAttribute(XmlResourceParser xml, String name) {
|
|
||||||
int resId = xml.getAttributeResourceValue(null, name, 0);
|
|
||||||
if (resId == 0) {
|
|
||||||
return xml.getAttributeValue(null, name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return getString(resId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Provider findProviderForDomain(String domain) {
|
|
||||||
try {
|
|
||||||
XmlResourceParser xml = getResources().getXml(R.xml.providers);
|
|
||||||
int xmlEventType;
|
|
||||||
Provider provider = null;
|
|
||||||
while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
|
|
||||||
if (xmlEventType == XmlResourceParser.START_TAG
|
|
||||||
&& "provider".equals(xml.getName())
|
|
||||||
&& domain.equalsIgnoreCase(getXmlAttribute(xml, "domain"))) {
|
|
||||||
provider = new Provider();
|
|
||||||
provider.id = getXmlAttribute(xml, "id");
|
|
||||||
provider.label = getXmlAttribute(xml, "label");
|
|
||||||
provider.domain = getXmlAttribute(xml, "domain");
|
|
||||||
provider.note = getXmlAttribute(xml, "note");
|
|
||||||
}
|
|
||||||
else if (xmlEventType == XmlResourceParser.START_TAG
|
|
||||||
&& "incoming".equals(xml.getName())
|
|
||||||
&& provider != null) {
|
|
||||||
provider.incomingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
|
|
||||||
provider.incomingUsernameTemplate = getXmlAttribute(xml, "username");
|
|
||||||
}
|
|
||||||
else if (xmlEventType == XmlResourceParser.START_TAG
|
|
||||||
&& "outgoing".equals(xml.getName())
|
|
||||||
&& provider != null) {
|
|
||||||
provider.outgoingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
|
|
||||||
provider.outgoingUsernameTemplate = getXmlAttribute(xml, "username");
|
|
||||||
}
|
|
||||||
else if (xmlEventType == XmlResourceParser.END_TAG
|
|
||||||
&& "provider".equals(xml.getName())
|
|
||||||
&& provider != null) {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
Log.e(k9.LOG_TAG, "Error while trying to load provider settings.", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Provider implements Serializable {
|
|
||||||
private static final long serialVersionUID = 8511656164616538989L;
|
|
||||||
|
|
||||||
public String id;
|
|
||||||
|
|
||||||
public String label;
|
|
||||||
|
|
||||||
public String domain;
|
|
||||||
|
|
||||||
public URI incomingUriTemplate;
|
|
||||||
|
|
||||||
public String incomingUsernameTemplate;
|
|
||||||
|
|
||||||
public URI outgoingUriTemplate;
|
|
||||||
|
|
||||||
public String outgoingUsernameTemplate;
|
|
||||||
|
|
||||||
public String note;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.mail.AuthenticationFailedException;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Store;
|
|
||||||
import com.fsck.k9.mail.Transport;
|
|
||||||
import com.fsck.k9.mail.CertificateValidationException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the given settings to make sure that they can be used to send and
|
|
||||||
* receive mail.
|
|
||||||
*
|
|
||||||
* XXX NOTE: The manifest for this app has it ignore config changes, because
|
|
||||||
* it doesn't correctly deal with restarting while its thread is running.
|
|
||||||
*/
|
|
||||||
public class AccountSetupCheckSettings extends Activity implements OnClickListener {
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
|
|
||||||
private static final String EXTRA_CHECK_INCOMING = "checkIncoming";
|
|
||||||
|
|
||||||
private static final String EXTRA_CHECK_OUTGOING = "checkOutgoing";
|
|
||||||
|
|
||||||
private Handler mHandler = new Handler();
|
|
||||||
|
|
||||||
private ProgressBar mProgressBar;
|
|
||||||
|
|
||||||
private TextView mMessageView;
|
|
||||||
|
|
||||||
private Account mAccount;
|
|
||||||
|
|
||||||
private boolean mCheckIncoming;
|
|
||||||
|
|
||||||
private boolean mCheckOutgoing;
|
|
||||||
|
|
||||||
private boolean mCanceled;
|
|
||||||
|
|
||||||
private boolean mDestroyed;
|
|
||||||
|
|
||||||
public static void actionCheckSettings(Activity context, Account account,
|
|
||||||
boolean checkIncoming, boolean checkOutgoing) {
|
|
||||||
Intent i = new Intent(context, AccountSetupCheckSettings.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
i.putExtra(EXTRA_CHECK_INCOMING, checkIncoming);
|
|
||||||
i.putExtra(EXTRA_CHECK_OUTGOING, checkOutgoing);
|
|
||||||
context.startActivityForResult(i, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.account_setup_check_settings);
|
|
||||||
mMessageView = (TextView)findViewById(R.id.message);
|
|
||||||
mProgressBar = (ProgressBar)findViewById(R.id.progress);
|
|
||||||
((Button)findViewById(R.id.cancel)).setOnClickListener(this);
|
|
||||||
|
|
||||||
setMessage(R.string.account_setup_check_settings_retr_info_msg);
|
|
||||||
mProgressBar.setIndeterminate(true);
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
mCheckIncoming = (boolean)getIntent().getBooleanExtra(EXTRA_CHECK_INCOMING, false);
|
|
||||||
mCheckOutgoing = (boolean)getIntent().getBooleanExtra(EXTRA_CHECK_OUTGOING, false);
|
|
||||||
|
|
||||||
new Thread() {
|
|
||||||
public void run() {
|
|
||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
||||||
try {
|
|
||||||
if (mDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mCanceled) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mCheckIncoming) {
|
|
||||||
setMessage(R.string.account_setup_check_settings_check_incoming_msg);
|
|
||||||
Store store = Store.getInstance(mAccount.getStoreUri(), getApplication());
|
|
||||||
store.checkSettings();
|
|
||||||
}
|
|
||||||
if (mDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mCanceled) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mCheckOutgoing) {
|
|
||||||
setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
|
|
||||||
Transport transport = Transport.getInstance(mAccount.getTransportUri());
|
|
||||||
transport.close();
|
|
||||||
transport.open();
|
|
||||||
transport.close();
|
|
||||||
}
|
|
||||||
if (mDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mCanceled) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setResult(RESULT_OK);
|
|
||||||
finish();
|
|
||||||
} catch (final AuthenticationFailedException afe) {
|
|
||||||
showErrorDialog(
|
|
||||||
R.string.account_setup_failed_dlg_auth_message_fmt,
|
|
||||||
afe.getMessage() == null ? "" : afe.getMessage());
|
|
||||||
} catch (final CertificateValidationException cve) {
|
|
||||||
showErrorDialog(
|
|
||||||
R.string.account_setup_failed_dlg_certificate_message_fmt,
|
|
||||||
cve.getMessage() == null ? "" : cve.getMessage());
|
|
||||||
} catch (final MessagingException me) {
|
|
||||||
showErrorDialog(
|
|
||||||
R.string.account_setup_failed_dlg_server_message_fmt,
|
|
||||||
me.getMessage() == null ? "" : me.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
mDestroyed = true;
|
|
||||||
mCanceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setMessage(final int resId) {
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (mDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mMessageView.setText(getString(resId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showErrorDialog(final int msgResId, final Object... args) {
|
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (mDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mProgressBar.setIndeterminate(false);
|
|
||||||
new AlertDialog.Builder(AccountSetupCheckSettings.this)
|
|
||||||
.setTitle(getString(R.string.account_setup_failed_dlg_title))
|
|
||||||
.setMessage(getString(msgResId, args))
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(
|
|
||||||
getString(R.string.account_setup_failed_dlg_edit_details_action),
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCancel() {
|
|
||||||
mCanceled = true;
|
|
||||||
setMessage(R.string.account_setup_check_settings_canceling_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
switch (v.getId()) {
|
|
||||||
case R.id.cancel:
|
|
||||||
onCancel();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
|
|
||||||
public class AccountSetupComposition extends Activity {
|
|
||||||
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
|
|
||||||
private Account mAccount;
|
|
||||||
|
|
||||||
private EditText mAccountSignature;
|
|
||||||
private EditText mAccountEmail;
|
|
||||||
private EditText mAccountAlwaysBcc;
|
|
||||||
private EditText mAccountName;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void actionEditCompositionSettings(Activity context, Account account) {
|
|
||||||
Intent i = new Intent(context, AccountSetupComposition.class);
|
|
||||||
i.setAction(Intent.ACTION_EDIT);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
|
|
||||||
setContentView(R.layout.account_setup_composition);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're being reloaded we override the original account with the one
|
|
||||||
* we saved
|
|
||||||
*/
|
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
|
|
||||||
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
mAccountName = (EditText)findViewById(R.id.account_name);
|
|
||||||
mAccountName.setText(mAccount.getName());
|
|
||||||
|
|
||||||
mAccountEmail = (EditText)findViewById(R.id.account_email);
|
|
||||||
mAccountEmail.setText(mAccount.getEmail());
|
|
||||||
|
|
||||||
mAccountAlwaysBcc = (EditText)findViewById(R.id.account_always_bcc);
|
|
||||||
mAccountAlwaysBcc.setText(mAccount.getAlwaysBcc());
|
|
||||||
|
|
||||||
mAccountSignature = (EditText)findViewById(R.id.account_signature);
|
|
||||||
mAccountSignature.setText(mAccount.getSignature());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
mAccount.refresh(Preferences.getPreferences(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveSettings() {
|
|
||||||
mAccount.setEmail(mAccountEmail.getText().toString());
|
|
||||||
mAccount.setAlwaysBcc(mAccountAlwaysBcc.getText().toString());
|
|
||||||
mAccount.setName(mAccountName.getText().toString());
|
|
||||||
mAccount.setSignature(mAccountSignature.getText().toString());
|
|
||||||
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,325 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.text.method.DigitsKeyListener;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
|
|
||||||
public class AccountSetupIncoming extends Activity implements OnClickListener {
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
|
|
||||||
|
|
||||||
private static final int popPorts[] = {
|
|
||||||
110, 995, 995, 110, 110
|
|
||||||
};
|
|
||||||
private static final String popSchemes[] = {
|
|
||||||
"pop3", "pop3+ssl", "pop3+ssl+", "pop3+tls", "pop3+tls+"
|
|
||||||
};
|
|
||||||
private static final int imapPorts[] = {
|
|
||||||
143, 993, 993, 143, 143
|
|
||||||
};
|
|
||||||
private static final String imapSchemes[] = {
|
|
||||||
"imap", "imap+ssl", "imap+ssl+", "imap+tls", "imap+tls+"
|
|
||||||
};
|
|
||||||
|
|
||||||
private int mAccountPorts[];
|
|
||||||
private String mAccountSchemes[];
|
|
||||||
private EditText mUsernameView;
|
|
||||||
private EditText mPasswordView;
|
|
||||||
private EditText mServerView;
|
|
||||||
private EditText mPortView;
|
|
||||||
private Spinner mSecurityTypeView;
|
|
||||||
private Spinner mDeletePolicyView;
|
|
||||||
private EditText mImapPathPrefixView;
|
|
||||||
private Button mNextButton;
|
|
||||||
private Account mAccount;
|
|
||||||
private boolean mMakeDefault;
|
|
||||||
|
|
||||||
public static void actionIncomingSettings(Activity context, Account account, boolean makeDefault) {
|
|
||||||
Intent i = new Intent(context, AccountSetupIncoming.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void actionEditIncomingSettings(Activity context, Account account) {
|
|
||||||
Intent i = new Intent(context, AccountSetupIncoming.class);
|
|
||||||
i.setAction(Intent.ACTION_EDIT);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.account_setup_incoming);
|
|
||||||
|
|
||||||
mUsernameView = (EditText)findViewById(R.id.account_username);
|
|
||||||
mPasswordView = (EditText)findViewById(R.id.account_password);
|
|
||||||
TextView serverLabelView = (TextView) findViewById(R.id.account_server_label);
|
|
||||||
mServerView = (EditText)findViewById(R.id.account_server);
|
|
||||||
mPortView = (EditText)findViewById(R.id.account_port);
|
|
||||||
mSecurityTypeView = (Spinner)findViewById(R.id.account_security_type);
|
|
||||||
mDeletePolicyView = (Spinner)findViewById(R.id.account_delete_policy);
|
|
||||||
mImapPathPrefixView = (EditText)findViewById(R.id.imap_path_prefix);
|
|
||||||
mNextButton = (Button)findViewById(R.id.next);
|
|
||||||
|
|
||||||
mNextButton.setOnClickListener(this);
|
|
||||||
|
|
||||||
SpinnerOption securityTypes[] = {
|
|
||||||
new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)),
|
|
||||||
new SpinnerOption(1,
|
|
||||||
getString(R.string.account_setup_incoming_security_ssl_optional_label)),
|
|
||||||
new SpinnerOption(2, getString(R.string.account_setup_incoming_security_ssl_label)),
|
|
||||||
new SpinnerOption(3,
|
|
||||||
getString(R.string.account_setup_incoming_security_tls_optional_label)),
|
|
||||||
new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)),
|
|
||||||
};
|
|
||||||
|
|
||||||
SpinnerOption deletePolicies[] = {
|
|
||||||
new SpinnerOption(0,
|
|
||||||
getString(R.string.account_setup_incoming_delete_policy_never_label)),
|
|
||||||
new SpinnerOption(1,
|
|
||||||
getString(R.string.account_setup_incoming_delete_policy_7days_label)),
|
|
||||||
new SpinnerOption(2,
|
|
||||||
getString(R.string.account_setup_incoming_delete_policy_delete_label)),
|
|
||||||
};
|
|
||||||
|
|
||||||
ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this,
|
|
||||||
android.R.layout.simple_spinner_item, securityTypes);
|
|
||||||
securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
mSecurityTypeView.setAdapter(securityTypesAdapter);
|
|
||||||
|
|
||||||
ArrayAdapter<SpinnerOption> deletePoliciesAdapter = new ArrayAdapter<SpinnerOption>(this,
|
|
||||||
android.R.layout.simple_spinner_item, deletePolicies);
|
|
||||||
deletePoliciesAdapter
|
|
||||||
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
mDeletePolicyView.setAdapter(deletePoliciesAdapter);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Updates the port when the user changes the security type. This allows
|
|
||||||
* us to show a reasonable default which the user can change.
|
|
||||||
*/
|
|
||||||
mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
||||||
public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
|
|
||||||
updatePortFromSecurityType();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNothingSelected(AdapterView<?> arg0) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calls validateFields() which enables or disables the Next button
|
|
||||||
* based on the fields' validity.
|
|
||||||
*/
|
|
||||||
TextWatcher validationTextWatcher = new TextWatcher() {
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mUsernameView.addTextChangedListener(validationTextWatcher);
|
|
||||||
mPasswordView.addTextChangedListener(validationTextWatcher);
|
|
||||||
mServerView.addTextChangedListener(validationTextWatcher);
|
|
||||||
mPortView.addTextChangedListener(validationTextWatcher);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Only allow digits in the port field.
|
|
||||||
*/
|
|
||||||
mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
mMakeDefault = (boolean)getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're being reloaded we override the original account with the one
|
|
||||||
* we saved
|
|
||||||
*/
|
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
|
|
||||||
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
URI uri = new URI(mAccount.getStoreUri());
|
|
||||||
String username = null;
|
|
||||||
String password = null;
|
|
||||||
if (uri.getUserInfo() != null) {
|
|
||||||
String[] userInfoParts = uri.getUserInfo().split(":", 2);
|
|
||||||
username = userInfoParts[0];
|
|
||||||
if (userInfoParts.length > 1) {
|
|
||||||
password = userInfoParts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username != null) {
|
|
||||||
mUsernameView.setText(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password != null) {
|
|
||||||
mPasswordView.setText(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.getScheme().startsWith("pop3")) {
|
|
||||||
serverLabelView.setText(R.string.account_setup_incoming_pop_server_label);
|
|
||||||
mAccountPorts = popPorts;
|
|
||||||
mAccountSchemes = popSchemes;
|
|
||||||
|
|
||||||
findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE);
|
|
||||||
} else if (uri.getScheme().startsWith("imap")) {
|
|
||||||
serverLabelView.setText(R.string.account_setup_incoming_imap_server_label);
|
|
||||||
mAccountPorts = imapPorts;
|
|
||||||
mAccountSchemes = imapSchemes;
|
|
||||||
|
|
||||||
findViewById(R.id.account_delete_policy_label).setVisibility(View.GONE);
|
|
||||||
mDeletePolicyView.setVisibility(View.GONE);
|
|
||||||
if (uri.getPath() != null && uri.getPath().length() > 0) {
|
|
||||||
mImapPathPrefixView.setText(uri.getPath().substring(1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error("Unknown account type: " + mAccount.getStoreUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < mAccountSchemes.length; i++) {
|
|
||||||
if (mAccountSchemes[i].equals(uri.getScheme())) {
|
|
||||||
SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mAccount.getDeletePolicy());
|
|
||||||
|
|
||||||
if (uri.getHost() != null) {
|
|
||||||
mServerView.setText(uri.getHost());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.getPort() != -1) {
|
|
||||||
mPortView.setText(Integer.toString(uri.getPort()));
|
|
||||||
} else {
|
|
||||||
updatePortFromSecurityType();
|
|
||||||
}
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* We should always be able to parse our own settings.
|
|
||||||
*/
|
|
||||||
throw new Error(use);
|
|
||||||
}
|
|
||||||
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateFields() {
|
|
||||||
mNextButton
|
|
||||||
.setEnabled(Utility.requiredFieldValid(mUsernameView)
|
|
||||||
&& Utility.requiredFieldValid(mPasswordView)
|
|
||||||
&& Utility.requiredFieldValid(mServerView)
|
|
||||||
&& Utility.requiredFieldValid(mPortView));
|
|
||||||
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePortFromSecurityType() {
|
|
||||||
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
|
|
||||||
mPortView.setText(Integer.toString(mAccountPorts[securityType]));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
if (Intent.ACTION_EDIT.equals(getIntent().getAction())) {
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
finish();
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* Set the username and password for the outgoing settings to the username and
|
|
||||||
* password the user just set for incoming.
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
URI oldUri = new URI(mAccount.getTransportUri());
|
|
||||||
URI uri = new URI(
|
|
||||||
oldUri.getScheme(),
|
|
||||||
mUsernameView.getText() + ":" + mPasswordView.getText(),
|
|
||||||
oldUri.getHost(),
|
|
||||||
oldUri.getPort(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
mAccount.setTransportUri(uri.toString());
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* If we can't set up the URL we just continue. It's only for
|
|
||||||
* convenience.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AccountSetupOutgoing.actionOutgoingSettings(this, mAccount, mMakeDefault);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNext() {
|
|
||||||
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
|
|
||||||
try {
|
|
||||||
String path = null;
|
|
||||||
if (mAccountSchemes[securityType].startsWith("imap")) {
|
|
||||||
path = "/" + mImapPathPrefixView.getText();
|
|
||||||
}
|
|
||||||
URI uri = new URI(
|
|
||||||
mAccountSchemes[securityType],
|
|
||||||
mUsernameView.getText() + ":" + mPasswordView.getText(),
|
|
||||||
mServerView.getText().toString(),
|
|
||||||
Integer.parseInt(mPortView.getText().toString()),
|
|
||||||
path, // path
|
|
||||||
null, // query
|
|
||||||
null);
|
|
||||||
mAccount.setStoreUri(uri.toString());
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* It's unrecoverable if we cannot create a URI from components that
|
|
||||||
* we validated to be safe.
|
|
||||||
*/
|
|
||||||
throw new Error(use);
|
|
||||||
}
|
|
||||||
|
|
||||||
mAccount.setDeletePolicy((Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value);
|
|
||||||
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
switch (v.getId()) {
|
|
||||||
case R.id.next:
|
|
||||||
onNext();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.text.method.TextKeyListener;
|
|
||||||
import android.text.method.TextKeyListener.Capitalize;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
import com.fsck.k9.activity.FolderMessageList;
|
|
||||||
|
|
||||||
public class AccountSetupNames extends Activity implements OnClickListener {
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
|
|
||||||
private EditText mDescription;
|
|
||||||
|
|
||||||
private EditText mName;
|
|
||||||
|
|
||||||
private Account mAccount;
|
|
||||||
|
|
||||||
private Button mDoneButton;
|
|
||||||
|
|
||||||
public static void actionSetNames(Context context, Account account) {
|
|
||||||
Intent i = new Intent(context, AccountSetupNames.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.account_setup_names);
|
|
||||||
mDescription = (EditText)findViewById(R.id.account_description);
|
|
||||||
mName = (EditText)findViewById(R.id.account_name);
|
|
||||||
mDoneButton = (Button)findViewById(R.id.done);
|
|
||||||
mDoneButton.setOnClickListener(this);
|
|
||||||
|
|
||||||
TextWatcher validationTextWatcher = new TextWatcher() {
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mName.addTextChangedListener(validationTextWatcher);
|
|
||||||
|
|
||||||
mName.setKeyListener(TextKeyListener.getInstance(false, Capitalize.WORDS));
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Since this field is considered optional, we don't set this here. If
|
|
||||||
* the user fills in a value we'll reset the current value, otherwise we
|
|
||||||
* just leave the saved value alone.
|
|
||||||
*/
|
|
||||||
// mDescription.setText(mAccount.getDescription());
|
|
||||||
if (mAccount.getName() != null) {
|
|
||||||
mName.setText(mAccount.getName());
|
|
||||||
}
|
|
||||||
if (!Utility.requiredFieldValid(mName)) {
|
|
||||||
mDoneButton.setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateFields() {
|
|
||||||
mDoneButton.setEnabled(Utility.requiredFieldValid(mName));
|
|
||||||
Utility.setCompoundDrawablesAlpha(mDoneButton, mDoneButton.isEnabled() ? 255 : 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNext() {
|
|
||||||
if (Utility.requiredFieldValid(mDescription)) {
|
|
||||||
mAccount.setDescription(mDescription.getText().toString());
|
|
||||||
}
|
|
||||||
mAccount.setName(mName.getText().toString());
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
FolderMessageList.actionHandleAccount(this, mAccount, k9.INBOX);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
switch (v.getId()) {
|
|
||||||
case R.id.done:
|
|
||||||
onNext();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
|
|
||||||
public class AccountSetupOptions extends Activity implements OnClickListener {
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
|
|
||||||
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
|
|
||||||
|
|
||||||
private Spinner mCheckFrequencyView;
|
|
||||||
|
|
||||||
private CheckBox mDefaultView;
|
|
||||||
|
|
||||||
private CheckBox mNotifyView;
|
|
||||||
|
|
||||||
private Account mAccount;
|
|
||||||
|
|
||||||
public static void actionOptions(Context context, Account account, boolean makeDefault) {
|
|
||||||
Intent i = new Intent(context, AccountSetupOptions.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.account_setup_options);
|
|
||||||
|
|
||||||
mCheckFrequencyView = (Spinner)findViewById(R.id.account_check_frequency);
|
|
||||||
mDefaultView = (CheckBox)findViewById(R.id.account_default);
|
|
||||||
mNotifyView = (CheckBox)findViewById(R.id.account_notify);
|
|
||||||
|
|
||||||
findViewById(R.id.next).setOnClickListener(this);
|
|
||||||
|
|
||||||
SpinnerOption checkFrequencies[] = {
|
|
||||||
new SpinnerOption(-1,
|
|
||||||
getString(R.string.account_setup_options_mail_check_frequency_never)),
|
|
||||||
new SpinnerOption(5,
|
|
||||||
getString(R.string.account_setup_options_mail_check_frequency_5min)),
|
|
||||||
new SpinnerOption(10,
|
|
||||||
getString(R.string.account_setup_options_mail_check_frequency_10min)),
|
|
||||||
new SpinnerOption(15,
|
|
||||||
getString(R.string.account_setup_options_mail_check_frequency_15min)),
|
|
||||||
new SpinnerOption(30,
|
|
||||||
getString(R.string.account_setup_options_mail_check_frequency_30min)),
|
|
||||||
new SpinnerOption(60,
|
|
||||||
getString(R.string.account_setup_options_mail_check_frequency_1hour)),
|
|
||||||
};
|
|
||||||
|
|
||||||
ArrayAdapter<SpinnerOption> checkFrequenciesAdapter = new ArrayAdapter<SpinnerOption>(this,
|
|
||||||
android.R.layout.simple_spinner_item, checkFrequencies);
|
|
||||||
checkFrequenciesAdapter
|
|
||||||
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
mCheckFrequencyView.setAdapter(checkFrequenciesAdapter);
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
boolean makeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
|
|
||||||
|
|
||||||
if (mAccount.equals(Preferences.getPreferences(this).getDefaultAccount()) || makeDefault) {
|
|
||||||
mDefaultView.setChecked(true);
|
|
||||||
}
|
|
||||||
mNotifyView.setChecked(mAccount.isNotifyNewMail());
|
|
||||||
SpinnerOption.setSpinnerOptionValue(mCheckFrequencyView, mAccount
|
|
||||||
.getAutomaticCheckIntervalMinutes());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDone() {
|
|
||||||
mAccount.setDescription(mAccount.getEmail());
|
|
||||||
mAccount.setNotifyNewMail(mNotifyView.isChecked());
|
|
||||||
mAccount.setAutomaticCheckIntervalMinutes((Integer)((SpinnerOption)mCheckFrequencyView
|
|
||||||
.getSelectedItem()).value);
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
if (mDefaultView.isChecked()) {
|
|
||||||
Preferences.getPreferences(this).setDefaultAccount(mAccount);
|
|
||||||
}
|
|
||||||
k9.setServicesEnabled(this);
|
|
||||||
AccountSetupNames.actionSetNames(this, mAccount);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
switch (v.getId()) {
|
|
||||||
case R.id.next:
|
|
||||||
onDone();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,266 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.text.method.DigitsKeyListener;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
|
|
||||||
public class AccountSetupOutgoing extends Activity implements OnClickListener,
|
|
||||||
OnCheckedChangeListener {
|
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
|
|
||||||
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
|
|
||||||
|
|
||||||
private static final int smtpPorts[] = {
|
|
||||||
25, 465, 465, 25, 25
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final String smtpSchemes[] = {
|
|
||||||
"smtp", "smtp+ssl", "smtp+ssl+", "smtp+tls", "smtp+tls+"
|
|
||||||
};
|
|
||||||
|
|
||||||
private EditText mUsernameView;
|
|
||||||
private EditText mPasswordView;
|
|
||||||
private EditText mServerView;
|
|
||||||
private EditText mPortView;
|
|
||||||
private CheckBox mRequireLoginView;
|
|
||||||
private ViewGroup mRequireLoginSettingsView;
|
|
||||||
private Spinner mSecurityTypeView;
|
|
||||||
private Button mNextButton;
|
|
||||||
private Account mAccount;
|
|
||||||
private boolean mMakeDefault;
|
|
||||||
|
|
||||||
public static void actionOutgoingSettings(Context context, Account account, boolean makeDefault) {
|
|
||||||
Intent i = new Intent(context, AccountSetupOutgoing.class);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void actionEditOutgoingSettings(Context context, Account account) {
|
|
||||||
Intent i = new Intent(context, AccountSetupOutgoing.class);
|
|
||||||
i.setAction(Intent.ACTION_EDIT);
|
|
||||||
i.putExtra(EXTRA_ACCOUNT, account);
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.account_setup_outgoing);
|
|
||||||
|
|
||||||
mUsernameView = (EditText)findViewById(R.id.account_username);
|
|
||||||
mPasswordView = (EditText)findViewById(R.id.account_password);
|
|
||||||
mServerView = (EditText)findViewById(R.id.account_server);
|
|
||||||
mPortView = (EditText)findViewById(R.id.account_port);
|
|
||||||
mRequireLoginView = (CheckBox)findViewById(R.id.account_require_login);
|
|
||||||
mRequireLoginSettingsView = (ViewGroup)findViewById(R.id.account_require_login_settings);
|
|
||||||
mSecurityTypeView = (Spinner)findViewById(R.id.account_security_type);
|
|
||||||
mNextButton = (Button)findViewById(R.id.next);
|
|
||||||
|
|
||||||
mNextButton.setOnClickListener(this);
|
|
||||||
mRequireLoginView.setOnCheckedChangeListener(this);
|
|
||||||
|
|
||||||
SpinnerOption securityTypes[] = {
|
|
||||||
new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)),
|
|
||||||
new SpinnerOption(1,
|
|
||||||
getString(R.string.account_setup_incoming_security_ssl_optional_label)),
|
|
||||||
new SpinnerOption(2, getString(R.string.account_setup_incoming_security_ssl_label)),
|
|
||||||
new SpinnerOption(3,
|
|
||||||
getString(R.string.account_setup_incoming_security_tls_optional_label)),
|
|
||||||
new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)),
|
|
||||||
};
|
|
||||||
|
|
||||||
ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this,
|
|
||||||
android.R.layout.simple_spinner_item, securityTypes);
|
|
||||||
securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
mSecurityTypeView.setAdapter(securityTypesAdapter);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Updates the port when the user changes the security type. This allows
|
|
||||||
* us to show a reasonable default which the user can change.
|
|
||||||
*/
|
|
||||||
mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
||||||
public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
|
|
||||||
updatePortFromSecurityType();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNothingSelected(AdapterView<?> arg0) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calls validateFields() which enables or disables the Next button
|
|
||||||
* based on the fields' validity.
|
|
||||||
*/
|
|
||||||
TextWatcher validationTextWatcher = new TextWatcher() {
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mUsernameView.addTextChangedListener(validationTextWatcher);
|
|
||||||
mPasswordView.addTextChangedListener(validationTextWatcher);
|
|
||||||
mServerView.addTextChangedListener(validationTextWatcher);
|
|
||||||
mPortView.addTextChangedListener(validationTextWatcher);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Only allow digits in the port field.
|
|
||||||
*/
|
|
||||||
mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
|
|
||||||
|
|
||||||
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
|
|
||||||
mMakeDefault = (boolean)getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're being reloaded we override the original account with the one
|
|
||||||
* we saved
|
|
||||||
*/
|
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
|
|
||||||
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
URI uri = new URI(mAccount.getTransportUri());
|
|
||||||
String username = null;
|
|
||||||
String password = null;
|
|
||||||
if (uri.getUserInfo() != null) {
|
|
||||||
String[] userInfoParts = uri.getUserInfo().split(":", 2);
|
|
||||||
username = userInfoParts[0];
|
|
||||||
if (userInfoParts.length > 1) {
|
|
||||||
password = userInfoParts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username != null) {
|
|
||||||
mUsernameView.setText(username);
|
|
||||||
mRequireLoginView.setChecked(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password != null) {
|
|
||||||
mPasswordView.setText(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < smtpSchemes.length; i++) {
|
|
||||||
if (smtpSchemes[i].equals(uri.getScheme())) {
|
|
||||||
SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.getHost() != null) {
|
|
||||||
mServerView.setText(uri.getHost());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.getPort() != -1) {
|
|
||||||
mPortView.setText(Integer.toString(uri.getPort()));
|
|
||||||
} else {
|
|
||||||
updatePortFromSecurityType();
|
|
||||||
}
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* We should always be able to parse our own settings.
|
|
||||||
*/
|
|
||||||
throw new Error(use);
|
|
||||||
}
|
|
||||||
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateFields() {
|
|
||||||
mNextButton
|
|
||||||
.setEnabled(
|
|
||||||
Utility.requiredFieldValid(mServerView) &&
|
|
||||||
Utility.requiredFieldValid(mPortView) &&
|
|
||||||
(!mRequireLoginView.isChecked() ||
|
|
||||||
(Utility.requiredFieldValid(mUsernameView) &&
|
|
||||||
Utility.requiredFieldValid(mPasswordView))));
|
|
||||||
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePortFromSecurityType() {
|
|
||||||
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
|
|
||||||
mPortView.setText(Integer.toString(smtpPorts[securityType]));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
if (Intent.ACTION_EDIT.equals(getIntent().getAction())) {
|
|
||||||
mAccount.save(Preferences.getPreferences(this));
|
|
||||||
finish();
|
|
||||||
} else {
|
|
||||||
AccountSetupOptions.actionOptions(this, mAccount, mMakeDefault);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNext() {
|
|
||||||
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
|
|
||||||
URI uri;
|
|
||||||
try {
|
|
||||||
String userInfo = null;
|
|
||||||
if (mRequireLoginView.isChecked()) {
|
|
||||||
userInfo = mUsernameView.getText().toString() + ":"
|
|
||||||
+ mPasswordView.getText().toString();
|
|
||||||
}
|
|
||||||
uri = new URI(smtpSchemes[securityType], userInfo, mServerView.getText().toString(),
|
|
||||||
Integer.parseInt(mPortView.getText().toString()), null, null, null);
|
|
||||||
mAccount.setTransportUri(uri.toString());
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
/*
|
|
||||||
* It's unrecoverable if we cannot create a URI from components that
|
|
||||||
* we validated to be safe.
|
|
||||||
*/
|
|
||||||
throw new Error(use);
|
|
||||||
}
|
|
||||||
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClick(View v) {
|
|
||||||
switch (v.getId()) {
|
|
||||||
case R.id.next:
|
|
||||||
onNext();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
|
||||||
mRequireLoginSettingsView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
|
|
||||||
validateFields();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.fsck.k9.activity.setup;
|
|
||||||
|
|
||||||
import android.widget.Spinner;
|
|
||||||
|
|
||||||
public class SpinnerOption {
|
|
||||||
public Object value;
|
|
||||||
|
|
||||||
public String label;
|
|
||||||
|
|
||||||
public static void setSpinnerOptionValue(Spinner spinner, Object value) {
|
|
||||||
for (int i = 0, count = spinner.getCount(); i < count; i++) {
|
|
||||||
SpinnerOption so = (SpinnerOption)spinner.getItemAtPosition(i);
|
|
||||||
if (so.value.equals(value)) {
|
|
||||||
spinner.setSelection(i, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpinnerOption(Object value, String label) {
|
|
||||||
this.value = value;
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,788 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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.codec.binary;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.BinaryDecoder;
|
|
||||||
import org.apache.commons.codec.BinaryEncoder;
|
|
||||||
import org.apache.commons.codec.DecoderException;
|
|
||||||
import org.apache.commons.codec.EncoderException;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Base64 encoding and decoding as defined by RFC 2045.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
|
|
||||||
* Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
|
|
||||||
* @author Apache Software Foundation
|
|
||||||
* @since 1.0-dev
|
|
||||||
* @version $Id$
|
|
||||||
*/
|
|
||||||
public class Base64 implements BinaryEncoder, BinaryDecoder {
|
|
||||||
/**
|
|
||||||
* Chunk size per RFC 2045 section 6.8.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
|
|
||||||
* equal signs.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
|
|
||||||
*/
|
|
||||||
static final int CHUNK_SIZE = 76;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chunk separator per RFC 2045 section 2.1.
|
|
||||||
*
|
|
||||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
|
|
||||||
*/
|
|
||||||
static final byte[] CHUNK_SEPARATOR = {'\r','\n'};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This array is a lookup table that translates 6-bit positive integer
|
|
||||||
* index values into their "Base64 Alphabet" equivalents as specified
|
|
||||||
* in Table 1 of RFC 2045.
|
|
||||||
*
|
|
||||||
* Thanks to "commons" project in ws.apache.org for this code.
|
|
||||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
|
||||||
*/
|
|
||||||
private static final byte[] intToBase64 = {
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
||||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
||||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Byte used to pad output.
|
|
||||||
*/
|
|
||||||
private static final byte PAD = '=';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This array is a lookup table that translates unicode characters
|
|
||||||
* drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
|
|
||||||
* into their 6-bit positive integer equivalents. Characters that
|
|
||||||
* are not in the Base64 alphabet but fall within the bounds of the
|
|
||||||
* array are translated to -1.
|
|
||||||
*
|
|
||||||
* Thanks to "commons" project in ws.apache.org for this code.
|
|
||||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
|
||||||
*/
|
|
||||||
private static final byte[] base64ToInt = {
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
|
|
||||||
55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
|
|
||||||
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
|
||||||
24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
|
|
||||||
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Mask used to extract 6 bits, used when encoding */
|
|
||||||
private static final int MASK_6BITS = 0x3f;
|
|
||||||
|
|
||||||
/** Mask used to extract 8 bits, used in decoding base64 bytes */
|
|
||||||
private static final int MASK_8BITS = 0xff;
|
|
||||||
|
|
||||||
// The static final fields above are used for the original static byte[] methods on Base64.
|
|
||||||
// The private member fields below are used with the new streaming approach, which requires
|
|
||||||
// some state be preserved between calls of encode() and decode().
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Line length for encoding. Not used when decoding. A value of zero or less implies
|
|
||||||
* no chunking of the base64 encoded data.
|
|
||||||
*/
|
|
||||||
private final int lineLength;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
|
|
||||||
*/
|
|
||||||
private final byte[] lineSeparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience variable to help us determine when our buffer is going to run out of
|
|
||||||
* room and needs resizing. <code>decodeSize = 3 + lineSeparator.length;</code>
|
|
||||||
*/
|
|
||||||
private final int decodeSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience variable to help us determine when our buffer is going to run out of
|
|
||||||
* room and needs resizing. <code>encodeSize = 4 + lineSeparator.length;</code>
|
|
||||||
*/
|
|
||||||
private final int encodeSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Buffer for streaming.
|
|
||||||
*/
|
|
||||||
private byte[] buf;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Position where next character should be written in the buffer.
|
|
||||||
*/
|
|
||||||
private int pos;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Position where next character should be read from the buffer.
|
|
||||||
*/
|
|
||||||
private int readPos;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Variable tracks how many characters have been written to the current line.
|
|
||||||
* Only used when encoding. We use it to make sure each encoded line never
|
|
||||||
* goes beyond lineLength (if lineLength > 0).
|
|
||||||
*/
|
|
||||||
private int currentLinePos;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes to the buffer only occur after every 3 reads when encoding, an
|
|
||||||
* every 4 reads when decoding. This variable helps track that.
|
|
||||||
*/
|
|
||||||
private int modulus;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Boolean flag to indicate the EOF has been reached. Once EOF has been
|
|
||||||
* reached, this Base64 object becomes useless, and must be thrown away.
|
|
||||||
*/
|
|
||||||
private boolean eof;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Place holder for the 3 bytes we're dealing with for our base64 logic.
|
|
||||||
* Bitwise operations store and extract the base64 encoding or decoding from
|
|
||||||
* this variable.
|
|
||||||
*/
|
|
||||||
private int x;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default constructor: lineLength is 76, and the lineSeparator is CRLF
|
|
||||||
* when encoding, and all forms can be decoded.
|
|
||||||
*/
|
|
||||||
public Base64() {
|
|
||||||
this(CHUNK_SIZE, CHUNK_SEPARATOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Consumer can use this constructor to choose a different lineLength
|
|
||||||
* when encoding (lineSeparator is still CRLF). All forms of data can
|
|
||||||
* be decoded.
|
|
||||||
* </p><p>
|
|
||||||
* Note: lineLengths that aren't multiples of 4 will still essentially
|
|
||||||
* end up being multiples of 4 in the encoded data.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param lineLength each line of encoded data will be at most this long
|
|
||||||
* (rounded up to nearest multiple of 4).
|
|
||||||
* If lineLength <= 0, then the output will not be divided into lines (chunks).
|
|
||||||
* Ignored when decoding.
|
|
||||||
*/
|
|
||||||
public Base64(int lineLength) {
|
|
||||||
this(lineLength, CHUNK_SEPARATOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Consumer can use this constructor to choose a different lineLength
|
|
||||||
* and lineSeparator when encoding. All forms of data can
|
|
||||||
* be decoded.
|
|
||||||
* </p><p>
|
|
||||||
* Note: lineLengths that aren't multiples of 4 will still essentially
|
|
||||||
* end up being multiples of 4 in the encoded data.
|
|
||||||
* </p>
|
|
||||||
* @param lineLength Each line of encoded data will be at most this long
|
|
||||||
* (rounded up to nearest multiple of 4). Ignored when decoding.
|
|
||||||
* If <= 0, then output will not be divided into lines (chunks).
|
|
||||||
* @param lineSeparator Each line of encoded data will end with this
|
|
||||||
* sequence of bytes.
|
|
||||||
* If lineLength <= 0, then the lineSeparator is not used.
|
|
||||||
* @throws IllegalArgumentException The provided lineSeparator included
|
|
||||||
* some base64 characters. That's not going to work!
|
|
||||||
*/
|
|
||||||
public Base64(int lineLength, byte[] lineSeparator) {
|
|
||||||
this.lineLength = lineLength;
|
|
||||||
this.lineSeparator = new byte[lineSeparator.length];
|
|
||||||
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
|
|
||||||
if (lineLength > 0) {
|
|
||||||
this.encodeSize = 4 + lineSeparator.length;
|
|
||||||
} else {
|
|
||||||
this.encodeSize = 4;
|
|
||||||
}
|
|
||||||
this.decodeSize = encodeSize - 1;
|
|
||||||
if (containsBase64Byte(lineSeparator)) {
|
|
||||||
String sep;
|
|
||||||
try {
|
|
||||||
sep = new String(lineSeparator, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException uee) {
|
|
||||||
sep = new String(lineSeparator);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this Base64 object has buffered data for reading.
|
|
||||||
*
|
|
||||||
* @return true if there is Base64 object still available for reading.
|
|
||||||
*/
|
|
||||||
boolean hasData() { return buf != null; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the amount of buffered data available for reading.
|
|
||||||
*
|
|
||||||
* @return The amount of buffered data available for reading.
|
|
||||||
*/
|
|
||||||
int avail() { return buf != null ? pos - readPos : 0; }
|
|
||||||
|
|
||||||
/** Doubles our buffer. */
|
|
||||||
private void resizeBuf() {
|
|
||||||
if (buf == null) {
|
|
||||||
buf = new byte[8192];
|
|
||||||
pos = 0;
|
|
||||||
readPos = 0;
|
|
||||||
} else {
|
|
||||||
byte[] b = new byte[buf.length * 2];
|
|
||||||
System.arraycopy(buf, 0, b, 0, buf.length);
|
|
||||||
buf = b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts buffered data into the provided byte[] array, starting
|
|
||||||
* at position bPos, up to a maximum of bAvail bytes. Returns how
|
|
||||||
* many bytes were actually extracted.
|
|
||||||
*
|
|
||||||
* @param b byte[] array to extract the buffered data into.
|
|
||||||
* @param bPos position in byte[] array to start extraction at.
|
|
||||||
* @param bAvail amount of bytes we're allowed to extract. We may extract
|
|
||||||
* fewer (if fewer are available).
|
|
||||||
* @return The number of bytes successfully extracted into the provided
|
|
||||||
* byte[] array.
|
|
||||||
*/
|
|
||||||
int readResults(byte[] b, int bPos, int bAvail) {
|
|
||||||
if (buf != null) {
|
|
||||||
int len = Math.min(avail(), bAvail);
|
|
||||||
if (buf != b) {
|
|
||||||
System.arraycopy(buf, readPos, b, bPos, len);
|
|
||||||
readPos += len;
|
|
||||||
if (readPos >= pos) {
|
|
||||||
buf = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Re-using the original consumer's output array is only
|
|
||||||
// allowed for one round.
|
|
||||||
buf = null;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
} else {
|
|
||||||
return eof ? -1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Small optimization where we try to buffer directly to the consumer's
|
|
||||||
* output array for one round (if consumer calls this method first!) instead
|
|
||||||
* of starting our own buffer.
|
|
||||||
*
|
|
||||||
* @param out byte[] array to buffer directly to.
|
|
||||||
* @param outPos Position to start buffering into.
|
|
||||||
* @param outAvail Amount of bytes available for direct buffering.
|
|
||||||
*/
|
|
||||||
void setInitialBuffer(byte[] out, int outPos, int outAvail) {
|
|
||||||
// We can re-use consumer's original output array under
|
|
||||||
// special circumstances, saving on some System.arraycopy().
|
|
||||||
if (out != null && out.length == outAvail) {
|
|
||||||
buf = out;
|
|
||||||
pos = outPos;
|
|
||||||
readPos = outPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Encodes all of the provided data, starting at inPos, for inAvail bytes.
|
|
||||||
* Must be called at least twice: once with the data to encode, and once
|
|
||||||
* with inAvail set to "-1" to alert encoder that EOF has been reached,
|
|
||||||
* so flush last remaining bytes (if not multiple of 3).
|
|
||||||
* </p><p>
|
|
||||||
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
|
|
||||||
* and general approach.
|
|
||||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param in byte[] array of binary data to base64 encode.
|
|
||||||
* @param inPos Position to start reading data from.
|
|
||||||
* @param inAvail Amount of bytes available from input for encoding.
|
|
||||||
*/
|
|
||||||
void encode(byte[] in, int inPos, int inAvail) {
|
|
||||||
if (eof) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// inAvail < 0 is how we're informed of EOF in the underlying data we're
|
|
||||||
// encoding.
|
|
||||||
if (inAvail < 0) {
|
|
||||||
eof = true;
|
|
||||||
if (buf == null || buf.length - pos < encodeSize) {
|
|
||||||
resizeBuf();
|
|
||||||
}
|
|
||||||
switch (modulus) {
|
|
||||||
case 1:
|
|
||||||
buf[pos++] = intToBase64[(x >> 2) & MASK_6BITS];
|
|
||||||
buf[pos++] = intToBase64[(x << 4) & MASK_6BITS];
|
|
||||||
buf[pos++] = PAD;
|
|
||||||
buf[pos++] = PAD;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
buf[pos++] = intToBase64[(x >> 10) & MASK_6BITS];
|
|
||||||
buf[pos++] = intToBase64[(x >> 4) & MASK_6BITS];
|
|
||||||
buf[pos++] = intToBase64[(x << 2) & MASK_6BITS];
|
|
||||||
buf[pos++] = PAD;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (lineLength > 0) {
|
|
||||||
System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
|
|
||||||
pos += lineSeparator.length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < inAvail; i++) {
|
|
||||||
if (buf == null || buf.length - pos < encodeSize) {
|
|
||||||
resizeBuf();
|
|
||||||
}
|
|
||||||
modulus = (++modulus) % 3;
|
|
||||||
int b = in[inPos++];
|
|
||||||
if (b < 0) { b += 256; }
|
|
||||||
x = (x << 8) + b;
|
|
||||||
if (0 == modulus) {
|
|
||||||
buf[pos++] = intToBase64[(x >> 18) & MASK_6BITS];
|
|
||||||
buf[pos++] = intToBase64[(x >> 12) & MASK_6BITS];
|
|
||||||
buf[pos++] = intToBase64[(x >> 6) & MASK_6BITS];
|
|
||||||
buf[pos++] = intToBase64[x & MASK_6BITS];
|
|
||||||
currentLinePos += 4;
|
|
||||||
if (lineLength > 0 && lineLength <= currentLinePos) {
|
|
||||||
System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
|
|
||||||
pos += lineSeparator.length;
|
|
||||||
currentLinePos = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Decodes all of the provided data, starting at inPos, for inAvail bytes.
|
|
||||||
* Should be called at least twice: once with the data to decode, and once
|
|
||||||
* with inAvail set to "-1" to alert decoder that EOF has been reached.
|
|
||||||
* The "-1" call is not necessary when decoding, but it doesn't hurt, either.
|
|
||||||
* </p><p>
|
|
||||||
* Ignores all non-base64 characters. This is how chunked (e.g. 76 character)
|
|
||||||
* data is handled, since CR and LF are silently ignored, but has implications
|
|
||||||
* for other bytes, too. This method subscribes to the garbage-in, garbage-out
|
|
||||||
* philosophy: it will not check the provided data for validity.
|
|
||||||
* </p><p>
|
|
||||||
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
|
|
||||||
* and general approach.
|
|
||||||
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
|
|
||||||
* </p>
|
|
||||||
|
|
||||||
* @param in byte[] array of ascii data to base64 decode.
|
|
||||||
* @param inPos Position to start reading data from.
|
|
||||||
* @param inAvail Amount of bytes available from input for encoding.
|
|
||||||
*/
|
|
||||||
void decode(byte[] in, int inPos, int inAvail) {
|
|
||||||
if (eof) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inAvail < 0) {
|
|
||||||
eof = true;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < inAvail; i++) {
|
|
||||||
if (buf == null || buf.length - pos < decodeSize) {
|
|
||||||
resizeBuf();
|
|
||||||
}
|
|
||||||
byte b = in[inPos++];
|
|
||||||
if (b == PAD) {
|
|
||||||
x = x << 6;
|
|
||||||
switch (modulus) {
|
|
||||||
case 2:
|
|
||||||
x = x << 6;
|
|
||||||
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
|
|
||||||
buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// WE'RE DONE!!!!
|
|
||||||
eof = true;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (b >= 0 && b < base64ToInt.length) {
|
|
||||||
int result = base64ToInt[b];
|
|
||||||
if (result >= 0) {
|
|
||||||
modulus = (++modulus) % 4;
|
|
||||||
x = (x << 6) + result;
|
|
||||||
if (modulus == 0) {
|
|
||||||
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
|
|
||||||
buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
|
|
||||||
buf[pos++] = (byte) (x & MASK_8BITS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the <code>octet</code> is in the base 64 alphabet.
|
|
||||||
*
|
|
||||||
* @param octet
|
|
||||||
* The value to test
|
|
||||||
* @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
|
|
||||||
*/
|
|
||||||
public static boolean isBase64(byte octet) {
|
|
||||||
return octet == PAD || (octet >= 0 && octet < base64ToInt.length && base64ToInt[octet] != -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
|
|
||||||
* Currently the method treats whitespace as valid.
|
|
||||||
*
|
|
||||||
* @param arrayOctet
|
|
||||||
* byte array to test
|
|
||||||
* @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is
|
|
||||||
* empty; false, otherwise
|
|
||||||
*/
|
|
||||||
public static boolean isArrayByteBase64(byte[] arrayOctet) {
|
|
||||||
for (int i = 0; i < arrayOctet.length; i++) {
|
|
||||||
if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
|
|
||||||
*
|
|
||||||
* @param arrayOctet
|
|
||||||
* byte array to test
|
|
||||||
* @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise
|
|
||||||
*/
|
|
||||||
private static boolean containsBase64Byte(byte[] arrayOctet) {
|
|
||||||
for (int i = 0; i < arrayOctet.length; i++) {
|
|
||||||
if (isBase64(arrayOctet[i])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes binary data using the base64 algorithm but does not chunk the output.
|
|
||||||
*
|
|
||||||
* @param binaryData
|
|
||||||
* binary data to encode
|
|
||||||
* @return Base64 characters
|
|
||||||
*/
|
|
||||||
public static byte[] encodeBase64(byte[] binaryData) {
|
|
||||||
return encodeBase64(binaryData, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
|
|
||||||
*
|
|
||||||
* @param binaryData
|
|
||||||
* binary data to encode
|
|
||||||
* @return Base64 characters chunked in 76 character blocks
|
|
||||||
*/
|
|
||||||
public static byte[] encodeBase64Chunked(byte[] binaryData) {
|
|
||||||
return encodeBase64(binaryData, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes an Object using the base64 algorithm. This method is provided in order to satisfy the requirements of the
|
|
||||||
* Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[].
|
|
||||||
*
|
|
||||||
* @param pObject
|
|
||||||
* Object to decode
|
|
||||||
* @return An object (of type byte[]) containing the binary data which corresponds to the byte[] supplied.
|
|
||||||
* @throws DecoderException
|
|
||||||
* if the parameter supplied is not of type byte[]
|
|
||||||
*/
|
|
||||||
public Object decode(Object pObject) throws DecoderException {
|
|
||||||
if (!(pObject instanceof byte[])) {
|
|
||||||
throw new DecoderException("Parameter supplied to Base64 decode is not a byte[]");
|
|
||||||
}
|
|
||||||
return decode((byte[]) pObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a byte[] containing containing characters in the Base64 alphabet.
|
|
||||||
*
|
|
||||||
* @param pArray
|
|
||||||
* A byte array containing Base64 character data
|
|
||||||
* @return a byte array containing binary data
|
|
||||||
*/
|
|
||||||
public byte[] decode(byte[] pArray) {
|
|
||||||
return decodeBase64(pArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
|
|
||||||
*
|
|
||||||
* @param binaryData
|
|
||||||
* Array containing binary data to encode.
|
|
||||||
* @param isChunked
|
|
||||||
* if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
|
|
||||||
* @return Base64-encoded data.
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
|
|
||||||
*/
|
|
||||||
public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
|
|
||||||
if (binaryData == null || binaryData.length == 0) {
|
|
||||||
return binaryData;
|
|
||||||
}
|
|
||||||
Base64 b64 = isChunked ? new Base64() : new Base64(0);
|
|
||||||
|
|
||||||
long len = (binaryData.length * 4) / 3;
|
|
||||||
long mod = len % 4;
|
|
||||||
if (mod != 0) {
|
|
||||||
len += 4 - mod;
|
|
||||||
}
|
|
||||||
if (isChunked) {
|
|
||||||
len += (1 + (len / CHUNK_SIZE)) * CHUNK_SEPARATOR.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > Integer.MAX_VALUE) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
|
|
||||||
}
|
|
||||||
byte[] buf = new byte[(int) len];
|
|
||||||
b64.setInitialBuffer(buf, 0, buf.length);
|
|
||||||
b64.encode(binaryData, 0, binaryData.length);
|
|
||||||
b64.encode(binaryData, 0, -1); // Notify encoder of EOF.
|
|
||||||
|
|
||||||
// Encoder might have resized, even though it was unnecessary.
|
|
||||||
if (b64.buf != buf) {
|
|
||||||
b64.readResults(buf, 0, buf.length);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes Base64 data into octets
|
|
||||||
*
|
|
||||||
* @param base64Data Byte array containing Base64 data
|
|
||||||
* @return Array containing decoded data.
|
|
||||||
*/
|
|
||||||
public static byte[] decodeBase64(byte[] base64Data) {
|
|
||||||
if (base64Data == null || base64Data.length == 0) {
|
|
||||||
return base64Data;
|
|
||||||
}
|
|
||||||
Base64 b64 = new Base64();
|
|
||||||
|
|
||||||
long len = (base64Data.length * 3) / 4;
|
|
||||||
byte[] buf = new byte[(int) len];
|
|
||||||
b64.setInitialBuffer(buf, 0, buf.length);
|
|
||||||
b64.decode(base64Data, 0, base64Data.length);
|
|
||||||
b64.decode(base64Data, 0, -1); // Notify decoder of EOF.
|
|
||||||
|
|
||||||
// We have no idea what the line-length was, so we
|
|
||||||
// cannot know how much of our array wasn't used.
|
|
||||||
byte[] result = new byte[b64.pos];
|
|
||||||
b64.readResults(result, 0, result.length);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discards any whitespace from a base-64 encoded block.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The base-64 encoded data to discard the whitespace from.
|
|
||||||
* @return The data, less whitespace (see RFC 2045).
|
|
||||||
* @deprecated This method is no longer needed
|
|
||||||
*/
|
|
||||||
static byte[] discardWhitespace(byte[] data) {
|
|
||||||
byte groomedData[] = new byte[data.length];
|
|
||||||
int bytesCopied = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
|
||||||
switch (data[i]) {
|
|
||||||
case ' ' :
|
|
||||||
case '\n' :
|
|
||||||
case '\r' :
|
|
||||||
case '\t' :
|
|
||||||
break;
|
|
||||||
default :
|
|
||||||
groomedData[bytesCopied++] = data[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte packedData[] = new byte[bytesCopied];
|
|
||||||
|
|
||||||
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
|
|
||||||
|
|
||||||
return packedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a byte value is whitespace or not.
|
|
||||||
*
|
|
||||||
* @param byteToCheck the byte to check
|
|
||||||
* @return true if byte is whitespace, false otherwise
|
|
||||||
*/
|
|
||||||
private static boolean isWhiteSpace(byte byteToCheck){
|
|
||||||
switch (byteToCheck) {
|
|
||||||
case ' ' :
|
|
||||||
case '\n' :
|
|
||||||
case '\r' :
|
|
||||||
case '\t' :
|
|
||||||
return true;
|
|
||||||
default :
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any
|
|
||||||
* characters outside of the base64 alphabet are to be ignored in base64 encoded data."
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The base-64 encoded data to groom
|
|
||||||
* @return The data, less non-base64 characters (see RFC 2045).
|
|
||||||
*/
|
|
||||||
static byte[] discardNonBase64(byte[] data) {
|
|
||||||
byte groomedData[] = new byte[data.length];
|
|
||||||
int bytesCopied = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
|
||||||
if (isBase64(data[i])) {
|
|
||||||
groomedData[bytesCopied++] = data[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte packedData[] = new byte[bytesCopied];
|
|
||||||
|
|
||||||
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
|
|
||||||
|
|
||||||
return packedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of the Encoder Interface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes an Object using the base64 algorithm. This method is provided in order to satisfy the requirements of the
|
|
||||||
* Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
|
|
||||||
*
|
|
||||||
* @param pObject
|
|
||||||
* Object to encode
|
|
||||||
* @return An object (of type byte[]) containing the base64 encoded data which corresponds to the byte[] supplied.
|
|
||||||
* @throws EncoderException
|
|
||||||
* if the parameter supplied is not of type byte[]
|
|
||||||
*/
|
|
||||||
public Object encode(Object pObject) throws EncoderException {
|
|
||||||
if (!(pObject instanceof byte[])) {
|
|
||||||
throw new EncoderException("Parameter supplied to Base64 encode is not a byte[]");
|
|
||||||
}
|
|
||||||
return encode((byte[]) pObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet.
|
|
||||||
*
|
|
||||||
* @param pArray
|
|
||||||
* a byte array containing binary data
|
|
||||||
* @return A byte array containing only Base64 character data
|
|
||||||
*/
|
|
||||||
public byte[] encode(byte[] pArray) {
|
|
||||||
return encodeBase64(pArray, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of integer encoding used for crypto
|
|
||||||
/**
|
|
||||||
* Decode a byte64-encoded integer according to crypto
|
|
||||||
* standards such as W3C's XML-Signature
|
|
||||||
*
|
|
||||||
* @param pArray a byte array containing base64 character data
|
|
||||||
* @return A BigInteger
|
|
||||||
*/
|
|
||||||
public static BigInteger decodeInteger(byte[] pArray) {
|
|
||||||
return new BigInteger(1, decodeBase64(pArray));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode to a byte64-encoded integer according to crypto
|
|
||||||
* standards such as W3C's XML-Signature
|
|
||||||
*
|
|
||||||
* @param bigInt a BigInteger
|
|
||||||
* @return A byte array containing base64 character data
|
|
||||||
* @throws NullPointerException if null is passed in
|
|
||||||
*/
|
|
||||||
public static byte[] encodeInteger(BigInteger bigInt) {
|
|
||||||
if(bigInt == null) {
|
|
||||||
throw new NullPointerException("encodeInteger called with null parameter");
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeBase64(toIntegerBytes(bigInt), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte-array representation of a <code>BigInteger</code>
|
|
||||||
* without sign bit.
|
|
||||||
*
|
|
||||||
* @param bigInt <code>BigInteger</code> to be converted
|
|
||||||
* @return a byte array representation of the BigInteger parameter
|
|
||||||
*/
|
|
||||||
static byte[] toIntegerBytes(BigInteger bigInt) {
|
|
||||||
int bitlen = bigInt.bitLength();
|
|
||||||
// round bitlen
|
|
||||||
bitlen = ((bitlen + 7) >> 3) << 3;
|
|
||||||
byte[] bigBytes = bigInt.toByteArray();
|
|
||||||
|
|
||||||
if(((bigInt.bitLength() % 8) != 0) &&
|
|
||||||
(((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
|
|
||||||
return bigBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up params for copying everything but sign bit
|
|
||||||
int startSrc = 0;
|
|
||||||
int len = bigBytes.length;
|
|
||||||
|
|
||||||
// if bigInt is exactly byte-aligned, just skip signbit in copy
|
|
||||||
if((bigInt.bitLength() % 8) == 0) {
|
|
||||||
startSrc = 1;
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
|
|
||||||
int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
|
|
||||||
byte[] resizedBytes = new byte[bitlen / 8];
|
|
||||||
|
|
||||||
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
|
|
||||||
|
|
||||||
return resizedBytes;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,179 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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.codec.binary;
|
|
||||||
|
|
||||||
import java.io.FilterOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Base64 encoding and decoding in a streaming fashion (unlimited size).
|
|
||||||
* When encoding the default lineLength is 76 characters and the default
|
|
||||||
* lineEnding is CRLF, but these can be overridden by using the appropriate
|
|
||||||
* constructor.
|
|
||||||
* <p>
|
|
||||||
* The default behaviour of the Base64OutputStream is to ENCODE, whereas the
|
|
||||||
* default behaviour of the Base64InputStream is to DECODE. But this behaviour
|
|
||||||
* can be overridden by using a different constructor.
|
|
||||||
* </p><p>
|
|
||||||
* This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
|
|
||||||
* Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author Apache Software Foundation
|
|
||||||
* @version $Id $
|
|
||||||
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
|
|
||||||
* @since 1.0-dev
|
|
||||||
*/
|
|
||||||
public class Base64OutputStream extends FilterOutputStream {
|
|
||||||
private final boolean doEncode;
|
|
||||||
private final Base64 base64;
|
|
||||||
private final byte[] singleByte = new byte[1];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Base64OutputStream such that all data written is Base64-encoded
|
|
||||||
* to the original provided OutputStream.
|
|
||||||
*
|
|
||||||
* @param out OutputStream to wrap.
|
|
||||||
*/
|
|
||||||
public Base64OutputStream(OutputStream out) {
|
|
||||||
this(out, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Base64OutputStream such that all data written is either
|
|
||||||
* Base64-encoded or Base64-decoded to the original provided OutputStream.
|
|
||||||
*
|
|
||||||
* @param out OutputStream to wrap.
|
|
||||||
* @param doEncode true if we should encode all data written to us,
|
|
||||||
* false if we should decode.
|
|
||||||
*/
|
|
||||||
public Base64OutputStream(OutputStream out, boolean doEncode) {
|
|
||||||
super(out);
|
|
||||||
this.doEncode = doEncode;
|
|
||||||
this.base64 = new Base64();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Base64OutputStream such that all data written is either
|
|
||||||
* Base64-encoded or Base64-decoded to the original provided OutputStream.
|
|
||||||
*
|
|
||||||
* @param out OutputStream to wrap.
|
|
||||||
* @param doEncode true if we should encode all data written to us,
|
|
||||||
* false if we should decode.
|
|
||||||
* @param lineLength If doEncode is true, each line of encoded
|
|
||||||
* data will contain lineLength characters.
|
|
||||||
* If lineLength <=0, the encoded data is not divided into lines.
|
|
||||||
* If doEncode is false, lineLength is ignored.
|
|
||||||
* @param lineSeparator If doEncode is true, each line of encoded
|
|
||||||
* data will be terminated with this byte sequence (e.g. \r\n).
|
|
||||||
* If lineLength <= 0, the lineSeparator is not used.
|
|
||||||
* If doEncode is false lineSeparator is ignored.
|
|
||||||
*/
|
|
||||||
public Base64OutputStream(OutputStream out, boolean doEncode, int lineLength, byte[] lineSeparator) {
|
|
||||||
super(out);
|
|
||||||
this.doEncode = doEncode;
|
|
||||||
this.base64 = new Base64(lineLength, lineSeparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the specified <code>byte</code> to this output stream.
|
|
||||||
*/
|
|
||||||
public void write(int i) throws IOException {
|
|
||||||
singleByte[0] = (byte) i;
|
|
||||||
write(singleByte, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes <code>len</code> bytes from the specified
|
|
||||||
* <code>b</code> array starting at <code>offset</code> to
|
|
||||||
* this output stream.
|
|
||||||
*
|
|
||||||
* @param b source byte array
|
|
||||||
* @param offset where to start reading the bytes
|
|
||||||
* @param len maximum number of bytes to write
|
|
||||||
*
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
* @throws NullPointerException if the byte array parameter is null
|
|
||||||
* @throws IndexOutOfBoundsException if offset, len or buffer size are invalid
|
|
||||||
*/
|
|
||||||
public void write(byte b[], int offset, int len) throws IOException {
|
|
||||||
if (b == null) {
|
|
||||||
throw new NullPointerException();
|
|
||||||
} else if (offset < 0 || len < 0 || offset + len < 0) {
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
} else if (offset > b.length || offset + len > b.length) {
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
} else if (len > 0) {
|
|
||||||
if (doEncode) {
|
|
||||||
base64.encode(b, offset, len);
|
|
||||||
} else {
|
|
||||||
base64.decode(b, offset, len);
|
|
||||||
}
|
|
||||||
flush(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes this output stream and forces any buffered output bytes
|
|
||||||
* to be written out to the stream. If propogate is true, the wrapped
|
|
||||||
* stream will also be flushed.
|
|
||||||
*
|
|
||||||
* @param propogate boolean flag to indicate whether the wrapped
|
|
||||||
* OutputStream should also be flushed.
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
*/
|
|
||||||
private void flush(boolean propogate) throws IOException {
|
|
||||||
int avail = base64.avail();
|
|
||||||
if (avail > 0) {
|
|
||||||
byte[] buf = new byte[avail];
|
|
||||||
int c = base64.readResults(buf, 0, avail);
|
|
||||||
if (c > 0) {
|
|
||||||
out.write(buf, 0, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (propogate) {
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes this output stream and forces any buffered output bytes
|
|
||||||
* to be written out to the stream.
|
|
||||||
*
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
*/
|
|
||||||
public void flush() throws IOException {
|
|
||||||
flush(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes this output stream, flushing any remaining bytes that must be encoded. The
|
|
||||||
* underlying stream is flushed but not closed.
|
|
||||||
*/
|
|
||||||
public void close() throws IOException {
|
|
||||||
// Notify encoder of EOF (-1).
|
|
||||||
if (doEncode) {
|
|
||||||
base64.encode(singleByte, 0, -1);
|
|
||||||
} else {
|
|
||||||
base64.decode(singleByte, 0, -1);
|
|
||||||
}
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.activity.MessageCompose;
|
|
||||||
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
|
||||||
import com.fsck.k9.mail.internet.MimeMessage;
|
|
||||||
import com.fsck.k9.service.BootReceiver;
|
|
||||||
import com.fsck.k9.service.MailService;
|
|
||||||
|
|
||||||
public class k9 extends Application {
|
|
||||||
public static final String LOG_TAG = "k9";
|
|
||||||
|
|
||||||
public static File tempDirectory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this is enabled there will be additional logging information sent to
|
|
||||||
* Log.d, including protocol dumps.
|
|
||||||
*/
|
|
||||||
public static boolean DEBUG = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this is enabled than logging that normally hides sensitive information
|
|
||||||
* like passwords will show that information.
|
|
||||||
*/
|
|
||||||
public static boolean DEBUG_SENSITIVE = false;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The MIME type(s) of attachments we're willing to send. At the moment it is not possible
|
|
||||||
* to open a chooser with a list of filter types, so the chooser is only opened with the first
|
|
||||||
* item in the list. The entire list will be used to filter down attachments that are added
|
|
||||||
* with Intent.ACTION_SEND.
|
|
||||||
*/
|
|
||||||
public static final String[] ACCEPTABLE_ATTACHMENT_SEND_TYPES = new String[] {
|
|
||||||
"image/*",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The MIME type(s) of attachments we're willing to view.
|
|
||||||
*/
|
|
||||||
public static final String[] ACCEPTABLE_ATTACHMENT_VIEW_TYPES = new String[] {
|
|
||||||
"image/*",
|
|
||||||
"audio/*",
|
|
||||||
"text/*",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The MIME type(s) of attachments we're not willing to view.
|
|
||||||
*/
|
|
||||||
public static final String[] UNACCEPTABLE_ATTACHMENT_VIEW_TYPES = new String[] {
|
|
||||||
"image/gif",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The MIME type(s) of attachments we're willing to download to SD.
|
|
||||||
*/
|
|
||||||
public static final String[] ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
|
|
||||||
"image/*",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The MIME type(s) of attachments we're not willing to download to SD.
|
|
||||||
*/
|
|
||||||
public static final String[] UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
|
|
||||||
"image/gif",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The special name "INBOX" is used throughout the application to mean "Whatever folder
|
|
||||||
* the server refers to as the user's Inbox. Placed here to ease use.
|
|
||||||
*/
|
|
||||||
public static final String INBOX = "INBOX";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies how many messages will be shown in a folder by default. This number is set
|
|
||||||
* on each new folder and can be incremented with "Load more messages..." by the
|
|
||||||
* VISIBLE_LIMIT_INCREMENT
|
|
||||||
*/
|
|
||||||
public static final int DEFAULT_VISIBLE_LIMIT = 25;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of additional messages to load when a user selectes "Load more messages..."
|
|
||||||
*/
|
|
||||||
public static final int VISIBLE_LIMIT_INCREMENT = 25;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum size of an attachment we're willing to download (either View or Save)
|
|
||||||
* Attachments that are base64 encoded (most) will be about 1.375x their actual size
|
|
||||||
* so we should probably factor that in. A 5MB attachment will generally be around
|
|
||||||
* 6.8MB downloaded but only 5MB saved.
|
|
||||||
*/
|
|
||||||
public static final int MAX_ATTACHMENT_DOWNLOAD_SIZE = (5 * 1024 * 1024);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called throughout the application when the number of accounts has changed. This method
|
|
||||||
* enables or disables the Compose activity, the boot receiver and the service based on
|
|
||||||
* whether any accounts are configured.
|
|
||||||
*/
|
|
||||||
public static void setServicesEnabled(Context context) {
|
|
||||||
setServicesEnabled(context, Preferences.getPreferences(context).getAccounts().length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setServicesEnabled(Context context, boolean enabled) {
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
if (!enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
|
||||||
/*
|
|
||||||
* If no accounts now exist but the service is still enabled we're about to disable it
|
|
||||||
* so we'll reschedule to kill off any existing alarms.
|
|
||||||
*/
|
|
||||||
MailService.actionReschedule(context);
|
|
||||||
}
|
|
||||||
pm.setComponentEnabledSetting(
|
|
||||||
new ComponentName(context, MessageCompose.class),
|
|
||||||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
||||||
PackageManager.DONT_KILL_APP);
|
|
||||||
pm.setComponentEnabledSetting(
|
|
||||||
new ComponentName(context, BootReceiver.class),
|
|
||||||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
||||||
PackageManager.DONT_KILL_APP);
|
|
||||||
pm.setComponentEnabledSetting(
|
|
||||||
new ComponentName(context, MailService.class),
|
|
||||||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
||||||
PackageManager.DONT_KILL_APP);
|
|
||||||
if (enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
|
||||||
/*
|
|
||||||
* And now if accounts do exist then we've just enabled the service and we want to
|
|
||||||
* schedule alarms for the new accounts.
|
|
||||||
*/
|
|
||||||
MailService.actionReschedule(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
Preferences prefs = Preferences.getPreferences(this);
|
|
||||||
DEBUG = prefs.geteEnableDebugLogging();
|
|
||||||
DEBUG_SENSITIVE = prefs.getEnableSensitiveLogging();
|
|
||||||
MessagingController.getInstance(this).resetVisibleLimits(prefs.getAccounts());
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We have to give MimeMessage a temp directory because File.createTempFile(String, String)
|
|
||||||
* doesn't work in Android and MimeMessage does not have access to a Context.
|
|
||||||
*/
|
|
||||||
BinaryTempFileBody.setTempDirectory(getCacheDir());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.apache.james.mime4j.field.address.AddressList;
|
|
||||||
import org.apache.james.mime4j.field.address.Mailbox;
|
|
||||||
import org.apache.james.mime4j.field.address.MailboxList;
|
|
||||||
import org.apache.james.mime4j.field.address.NamedMailbox;
|
|
||||||
import org.apache.james.mime4j.field.address.parser.ParseException;
|
|
||||||
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
|
||||||
|
|
||||||
public class Address {
|
|
||||||
String mAddress;
|
|
||||||
|
|
||||||
String mPersonal;
|
|
||||||
|
|
||||||
public Address(String address, String personal) {
|
|
||||||
this.mAddress = address;
|
|
||||||
this.mPersonal = personal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Address(String address) {
|
|
||||||
this.mAddress = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return mAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAddress(String address) {
|
|
||||||
this.mAddress = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPersonal() {
|
|
||||||
return mPersonal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPersonal(String personal) {
|
|
||||||
this.mPersonal = personal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a comma separated list of addresses in RFC-822 format and return an
|
|
||||||
* array of Address objects.
|
|
||||||
*
|
|
||||||
* @param addressList
|
|
||||||
* @return An array of 0 or more Addresses.
|
|
||||||
*/
|
|
||||||
public static Address[] parse(String addressList) {
|
|
||||||
ArrayList<Address> addresses = new ArrayList<Address>();
|
|
||||||
if (addressList == null) {
|
|
||||||
return new Address[] {};
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
MailboxList parsedList = AddressList.parse(addressList).flatten();
|
|
||||||
for (int i = 0, count = parsedList.size(); i < count; i++) {
|
|
||||||
org.apache.james.mime4j.field.address.Address address = parsedList.get(i);
|
|
||||||
if (address instanceof NamedMailbox) {
|
|
||||||
NamedMailbox namedMailbox = (NamedMailbox)address;
|
|
||||||
addresses.add(new Address(namedMailbox.getLocalPart() + "@"
|
|
||||||
+ namedMailbox.getDomain(), namedMailbox.getName()));
|
|
||||||
} else if (address instanceof Mailbox) {
|
|
||||||
Mailbox mailbox = (Mailbox)address;
|
|
||||||
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain()));
|
|
||||||
} else {
|
|
||||||
Log.e(k9.LOG_TAG, "Unknown address type from Mime4J: "
|
|
||||||
+ address.getClass().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (ParseException pe) {
|
|
||||||
}
|
|
||||||
return addresses.toArray(new Address[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o instanceof Address) {
|
|
||||||
return getAddress().equals(((Address) o).getAddress());
|
|
||||||
}
|
|
||||||
return super.equals(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
if (mPersonal != null) {
|
|
||||||
if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
|
|
||||||
return Utility.quoteString(mPersonal) + " <" + mAddress + ">";
|
|
||||||
} else {
|
|
||||||
return mPersonal + " <" + mAddress + ">";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return mAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toString(Address[] addresses) {
|
|
||||||
if (addresses == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
for (int i = 0; i < addresses.length; i++) {
|
|
||||||
sb.append(addresses[i].toString());
|
|
||||||
if (i < addresses.length - 1) {
|
|
||||||
sb.append(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns either the personal portion of the Address or the address portion if the personal
|
|
||||||
* is not available.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String toFriendly() {
|
|
||||||
if (mPersonal != null && mPersonal.length() > 0) {
|
|
||||||
return mPersonal;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return mAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toFriendly(Address[] addresses) {
|
|
||||||
if (addresses == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
for (int i = 0; i < addresses.length; i++) {
|
|
||||||
sb.append(addresses[i].toFriendly());
|
|
||||||
if (i < addresses.length - 1) {
|
|
||||||
sb.append(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpacks an address list previously packed with packAddressList()
|
|
||||||
* @param list
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static Address[] unpack(String addressList) {
|
|
||||||
if (addressList == null) {
|
|
||||||
return new Address[] { };
|
|
||||||
}
|
|
||||||
ArrayList<Address> addresses = new ArrayList<Address>();
|
|
||||||
int length = addressList.length();
|
|
||||||
int pairStartIndex = 0;
|
|
||||||
int pairEndIndex = 0;
|
|
||||||
int addressEndIndex = 0;
|
|
||||||
while (pairStartIndex < length) {
|
|
||||||
pairEndIndex = addressList.indexOf(',', pairStartIndex);
|
|
||||||
if (pairEndIndex == -1) {
|
|
||||||
pairEndIndex = length;
|
|
||||||
}
|
|
||||||
addressEndIndex = addressList.indexOf(';', pairStartIndex);
|
|
||||||
String address = null;
|
|
||||||
String personal = null;
|
|
||||||
if (addressEndIndex == -1 || addressEndIndex > pairEndIndex) {
|
|
||||||
address = Utility.fastUrlDecode(addressList.substring(pairStartIndex, pairEndIndex));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
address = Utility.fastUrlDecode(addressList.substring(pairStartIndex, addressEndIndex));
|
|
||||||
personal = Utility.fastUrlDecode(addressList.substring(addressEndIndex + 1, pairEndIndex));
|
|
||||||
}
|
|
||||||
addresses.add(new Address(address, personal));
|
|
||||||
pairStartIndex = pairEndIndex + 1;
|
|
||||||
}
|
|
||||||
return addresses.toArray(new Address[] { });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Packs an address list into a String that is very quick to read
|
|
||||||
* and parse. Packed lists can be unpacked with unpackAddressList()
|
|
||||||
* The packed list is a comma seperated list of:
|
|
||||||
* URLENCODE(address)[;URLENCODE(personal)]
|
|
||||||
* @param list
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String pack(Address[] addresses) {
|
|
||||||
if (addresses == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
for (int i = 0, count = addresses.length; i < count; i++) {
|
|
||||||
Address address = addresses[i];
|
|
||||||
try {
|
|
||||||
sb.append(URLEncoder.encode(address.getAddress(), "UTF-8"));
|
|
||||||
if (address.getPersonal() != null) {
|
|
||||||
sb.append(';');
|
|
||||||
sb.append(URLEncoder.encode(address.getPersonal(), "UTF-8"));
|
|
||||||
}
|
|
||||||
if (i < count - 1) {
|
|
||||||
sb.append(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException uee) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
public class AuthenticationFailedException extends MessagingException {
|
|
||||||
public static final long serialVersionUID = -1;
|
|
||||||
|
|
||||||
public AuthenticationFailedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthenticationFailedException(String message, Throwable throwable) {
|
|
||||||
super(message, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public interface Body {
|
|
||||||
public InputStream getInputStream() throws MessagingException;
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
public abstract class BodyPart implements Part {
|
|
||||||
protected Multipart mParent;
|
|
||||||
|
|
||||||
public Multipart getParent() {
|
|
||||||
return mParent;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
public class CertificateValidationException extends MessagingException {
|
|
||||||
public static final long serialVersionUID = -1;
|
|
||||||
|
|
||||||
public CertificateValidationException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CertificateValidationException(String message, Throwable throwable) {
|
|
||||||
super(message, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* A FetchProfile is a list of items that should be downloaded in bulk for a set of messages.
|
|
||||||
* FetchProfile can contain the following objects:
|
|
||||||
* FetchProfile.Item: Described below.
|
|
||||||
* Message: Indicates that the body of the entire message should be fetched.
|
|
||||||
* Synonymous with FetchProfile.Item.BODY.
|
|
||||||
* Part: Indicates that the given Part should be fetched. The provider
|
|
||||||
* is expected have previously created the given BodyPart and stored
|
|
||||||
* any information it needs to download the content.
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
public class FetchProfile extends ArrayList {
|
|
||||||
/**
|
|
||||||
* Default items available for pre-fetching. It should be expected that any
|
|
||||||
* item fetched by using these items could potentially include all of the
|
|
||||||
* previous items.
|
|
||||||
*/
|
|
||||||
public enum Item {
|
|
||||||
/**
|
|
||||||
* Download the flags of the message.
|
|
||||||
*/
|
|
||||||
FLAGS,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download the envelope of the message. This should include at minimum
|
|
||||||
* the size and the following headers: date, subject, from, content-type, to, cc
|
|
||||||
*/
|
|
||||||
ENVELOPE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download the structure of the message. This maps directly to IMAP's BODYSTRUCTURE
|
|
||||||
* and may map to other providers.
|
|
||||||
* The provider should, if possible, fill in a properly formatted MIME structure in
|
|
||||||
* the message without actually downloading any message data. If the provider is not
|
|
||||||
* capable of this operation it should specifically set the body of the message to null
|
|
||||||
* so that upper levels can detect that a full body download is needed.
|
|
||||||
*/
|
|
||||||
STRUCTURE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A sane portion of the entire message, cut off at a provider determined limit.
|
|
||||||
* This should generaly be around 50kB.
|
|
||||||
*/
|
|
||||||
BODY_SANE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entire message.
|
|
||||||
*/
|
|
||||||
BODY,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flags that can be applied to Messages.
|
|
||||||
*/
|
|
||||||
public enum Flag {
|
|
||||||
DELETED,
|
|
||||||
SEEN,
|
|
||||||
ANSWERED,
|
|
||||||
FLAGGED,
|
|
||||||
DRAFT,
|
|
||||||
RECENT,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The following flags are for internal library use only.
|
|
||||||
* TODO Eventually we should creates a Flags class that extends ArrayList that allows
|
|
||||||
* these flags and Strings to represent user defined flags. At that point the below
|
|
||||||
* flags should become user defined flags.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Delete and remove from the LocalStore immediately.
|
|
||||||
*/
|
|
||||||
X_DESTROYED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sending of an unsent message failed. It will be retried. Used to show status.
|
|
||||||
*/
|
|
||||||
X_SEND_FAILED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sending of an unsent message is in progress.
|
|
||||||
*/
|
|
||||||
X_SEND_IN_PROGRESS,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that a message is fully downloaded from the server and can be viewed normally.
|
|
||||||
* This does not include attachments, which are never downloaded fully.
|
|
||||||
*/
|
|
||||||
X_DOWNLOADED_FULL,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that a message is partially downloaded from the server and can be viewed but
|
|
||||||
* more content is available on the server.
|
|
||||||
* This does not include attachments, which are never downloaded fully.
|
|
||||||
*/
|
|
||||||
X_DOWNLOADED_PARTIAL,
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class Folder {
|
|
||||||
public enum OpenMode {
|
|
||||||
READ_WRITE, READ_ONLY,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FolderType {
|
|
||||||
HOLDS_FOLDERS, HOLDS_MESSAGES,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forces an open of the MailProvider. If the provider is already open this
|
|
||||||
* function returns without doing anything.
|
|
||||||
*
|
|
||||||
* @param mode READ_ONLY or READ_WRITE
|
|
||||||
*/
|
|
||||||
public abstract void open(OpenMode mode) throws MessagingException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forces a close of the MailProvider. Any further access will attempt to
|
|
||||||
* reopen the MailProvider.
|
|
||||||
*
|
|
||||||
* @param expunge If true all deleted messages will be expunged.
|
|
||||||
*/
|
|
||||||
public abstract void close(boolean expunge) throws MessagingException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return True if further commands are not expected to have to open the
|
|
||||||
* connection.
|
|
||||||
*/
|
|
||||||
public abstract boolean isOpen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mode the folder was opened with. This may be different than the mode the open
|
|
||||||
* was requested with.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public abstract OpenMode getMode() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract boolean create(FolderType type) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract boolean exists() throws MessagingException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A count of the messages in the selected folder.
|
|
||||||
*/
|
|
||||||
public abstract int getMessageCount() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract int getUnreadMessageCount() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Message getMessage(String uid) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
|
||||||
throws MessagingException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the given list of messages. The specified listener is notified as
|
|
||||||
* each fetch completes. Messages are downloaded as (as) lightweight (as
|
|
||||||
* possible) objects to be filled in with later requests. In most cases this
|
|
||||||
* means that only the UID is downloaded.
|
|
||||||
*
|
|
||||||
* @param uids
|
|
||||||
* @param listener
|
|
||||||
*/
|
|
||||||
public abstract Message[] getMessages(MessageRetrievalListener listener)
|
|
||||||
throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
|
||||||
throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void appendMessages(Message[] messages) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void copyMessages(Message[] msgs, Folder folder) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setFlags(Message[] messages, Flag[] flags, boolean value)
|
|
||||||
throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Message[] expunge() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void fetch(Message[] messages, FetchProfile fp,
|
|
||||||
MessageRetrievalListener listener) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void delete(boolean recurse) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract String getName();
|
|
||||||
|
|
||||||
public abstract Flag[] getPermanentFlags() throws MessagingException;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public abstract class Message implements Part, Body {
|
|
||||||
public enum RecipientType {
|
|
||||||
TO, CC, BCC,
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String mUid;
|
|
||||||
|
|
||||||
protected HashSet<Flag> mFlags = new HashSet<Flag>();
|
|
||||||
|
|
||||||
protected Date mInternalDate;
|
|
||||||
|
|
||||||
protected Folder mFolder;
|
|
||||||
|
|
||||||
public String getUid() {
|
|
||||||
return mUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUid(String uid) {
|
|
||||||
this.mUid = uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Folder getFolder() {
|
|
||||||
return mFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract String getSubject() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setSubject(String subject) throws MessagingException;
|
|
||||||
|
|
||||||
public Date getInternalDate() {
|
|
||||||
return mInternalDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInternalDate(Date internalDate) {
|
|
||||||
this.mInternalDate = internalDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Date getReceivedDate() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Date getSentDate() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setSentDate(Date sentDate) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setRecipients(RecipientType type, Address[] addresses)
|
|
||||||
throws MessagingException;
|
|
||||||
|
|
||||||
public void setRecipient(RecipientType type, Address address) throws MessagingException {
|
|
||||||
setRecipients(type, new Address[] {
|
|
||||||
address
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Address[] getFrom() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setFrom(Address from) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Address[] getReplyTo() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setReplyTo(Address[] from) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Body getBody() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract String getContentType() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void addHeader(String name, String value) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setHeader(String name, String value) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract String[] getHeader(String name) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void removeHeader(String name) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setBody(Body body) throws MessagingException;
|
|
||||||
|
|
||||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
|
||||||
return getContentType().startsWith(mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO Refactor Flags at some point to be able to store user defined flags.
|
|
||||||
*/
|
|
||||||
public Flag[] getFlags() {
|
|
||||||
return mFlags.toArray(new Flag[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFlag(Flag flag, boolean set) throws MessagingException {
|
|
||||||
if (set) {
|
|
||||||
mFlags.add(flag);
|
|
||||||
} else {
|
|
||||||
mFlags.remove(flag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method calls setFlag(Flag, boolean)
|
|
||||||
* @param flags
|
|
||||||
* @param set
|
|
||||||
*/
|
|
||||||
public void setFlags(Flag[] flags, boolean set) throws MessagingException {
|
|
||||||
for (Flag flag : flags) {
|
|
||||||
setFlag(flag, set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSet(Flag flag) {
|
|
||||||
return mFlags.contains(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void saveChanges() throws MessagingException;
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
public class MessageDateComparator implements Comparator<Message> {
|
|
||||||
public int compare(Message o1, Message o2) {
|
|
||||||
try {
|
|
||||||
if (o1.getSentDate() == null) {
|
|
||||||
return 1;
|
|
||||||
} else if (o2.getSentDate() == null) {
|
|
||||||
return -1;
|
|
||||||
} else
|
|
||||||
return o2.getSentDate().compareTo(o1.getSentDate());
|
|
||||||
} catch (Exception e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
public interface MessageRetrievalListener {
|
|
||||||
public void messageStarted(String uid, int number, int ofTotal);
|
|
||||||
|
|
||||||
public void messageFinished(Message message, int number, int ofTotal);
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
public class MessagingException extends Exception {
|
|
||||||
public static final long serialVersionUID = -1;
|
|
||||||
|
|
||||||
public MessagingException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessagingException(String message, Throwable throwable) {
|
|
||||||
super(message, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public abstract class Multipart implements Body {
|
|
||||||
protected Part mParent;
|
|
||||||
|
|
||||||
protected ArrayList<BodyPart> mParts = new ArrayList<BodyPart>();
|
|
||||||
|
|
||||||
protected String mContentType;
|
|
||||||
|
|
||||||
public void addBodyPart(BodyPart part) throws MessagingException {
|
|
||||||
mParts.add(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addBodyPart(BodyPart part, int index) throws MessagingException {
|
|
||||||
mParts.add(index, part);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BodyPart getBodyPart(int index) throws MessagingException {
|
|
||||||
return mParts.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() throws MessagingException {
|
|
||||||
return mContentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() throws MessagingException {
|
|
||||||
return mParts.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeBodyPart(BodyPart part) throws MessagingException {
|
|
||||||
return mParts.remove(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeBodyPart(int index) throws MessagingException {
|
|
||||||
mParts.remove(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Part getParent() throws MessagingException {
|
|
||||||
return mParent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParent(Part parent) throws MessagingException {
|
|
||||||
this.mParent = parent;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
public class NoSuchProviderException extends MessagingException {
|
|
||||||
public static final long serialVersionUID = -1;
|
|
||||||
|
|
||||||
public NoSuchProviderException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoSuchProviderException(String message, Throwable throwable) {
|
|
||||||
super(message, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public interface Part {
|
|
||||||
public void addHeader(String name, String value) throws MessagingException;
|
|
||||||
|
|
||||||
public void removeHeader(String name) throws MessagingException;
|
|
||||||
|
|
||||||
public void setHeader(String name, String value) throws MessagingException;
|
|
||||||
|
|
||||||
public Body getBody() throws MessagingException;
|
|
||||||
|
|
||||||
public String getContentType() throws MessagingException;
|
|
||||||
|
|
||||||
public String getDisposition() throws MessagingException;
|
|
||||||
|
|
||||||
public String[] getHeader(String name) throws MessagingException;
|
|
||||||
|
|
||||||
public int getSize() throws MessagingException;
|
|
||||||
|
|
||||||
public boolean isMimeType(String mimeType) throws MessagingException;
|
|
||||||
|
|
||||||
public String getMimeType() throws MessagingException;
|
|
||||||
|
|
||||||
public void setBody(Body body) throws MessagingException;
|
|
||||||
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException;
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.store.ImapStore;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore;
|
|
||||||
import com.fsck.k9.mail.store.Pop3Store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store is the access point for an email message store. It's location can be
|
|
||||||
* local or remote and no specific protocol is defined. Store is intended to
|
|
||||||
* loosely model in combination the JavaMail classes javax.mail.Store and
|
|
||||||
* javax.mail.Folder along with some additional functionality to improve
|
|
||||||
* performance on mobile devices. Implementations of this class should focus on
|
|
||||||
* making as few network connections as possible.
|
|
||||||
*/
|
|
||||||
public abstract class Store {
|
|
||||||
/**
|
|
||||||
* A global suggestion to Store implementors on how much of the body
|
|
||||||
* should be returned on FetchProfile.Item.BODY_SANE requests.
|
|
||||||
*/
|
|
||||||
public static final int FETCH_BODY_SANE_SUGGESTED_SIZE = (50 * 1024);
|
|
||||||
|
|
||||||
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
|
|
||||||
protected static final int SOCKET_READ_TIMEOUT = 60000;
|
|
||||||
|
|
||||||
private static HashMap<String, Store> mStores = new HashMap<String, Store>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an instance of a mail store. The URI is parsed as a standard URI and
|
|
||||||
* the scheme is used to determine which protocol will be used. The
|
|
||||||
* following schemes are currently recognized: imap - IMAP with no
|
|
||||||
* connection security. Ex: imap://username:password@host/ imap+tls - IMAP
|
|
||||||
* with TLS connection security, if the server supports it. Ex:
|
|
||||||
* imap+tls://username:password@host imap+tls+ - IMAP with required TLS
|
|
||||||
* connection security. Connection fails if TLS is not available. Ex:
|
|
||||||
* imap+tls+://username:password@host imap+ssl+ - IMAP with required SSL
|
|
||||||
* connection security. Connection fails if SSL is not available. Ex:
|
|
||||||
* imap+ssl+://username:password@host
|
|
||||||
*
|
|
||||||
* @param uri The URI of the store.
|
|
||||||
* @return
|
|
||||||
* @throws MessagingException
|
|
||||||
*/
|
|
||||||
public synchronized static Store getInstance(String uri, Application application) throws MessagingException {
|
|
||||||
Store store = mStores.get(uri);
|
|
||||||
if (store == null) {
|
|
||||||
if (uri.startsWith("imap")) {
|
|
||||||
store = new ImapStore(uri);
|
|
||||||
} else if (uri.startsWith("pop3")) {
|
|
||||||
store = new Pop3Store(uri);
|
|
||||||
} else if (uri.startsWith("local")) {
|
|
||||||
store = new LocalStore(uri, application);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store != null) {
|
|
||||||
mStores.put(uri, store);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store == null) {
|
|
||||||
throw new MessagingException("Unable to locate an applicable Store for " + uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Folder getFolder(String name) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract Folder[] getPersonalNamespaces() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void checkSettings() throws MessagingException;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.transport.SmtpTransport;
|
|
||||||
|
|
||||||
public abstract class Transport {
|
|
||||||
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
|
|
||||||
|
|
||||||
public synchronized static Transport getInstance(String uri) throws MessagingException {
|
|
||||||
if (uri.startsWith("smtp")) {
|
|
||||||
return new SmtpTransport(uri);
|
|
||||||
} else {
|
|
||||||
throw new MessagingException("Unable to locate an applicable Transport for " + uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void open() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void sendMessage(Message message) throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void close() throws MessagingException;
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package com.fsck.k9.mail.internet;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.FilterInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.codec.binary.Base64OutputStream;
|
|
||||||
import com.fsck.k9.mail.Body;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Body that is backed by a temp file. The Body exposes a getOutputStream method that allows
|
|
||||||
* the user to write to the temp file. After the write the body is available via getInputStream
|
|
||||||
* and writeTo one time. After writeTo is called, or the InputStream returned from
|
|
||||||
* getInputStream is closed the file is deleted and the Body should be considered disposed of.
|
|
||||||
*/
|
|
||||||
public class BinaryTempFileBody implements Body {
|
|
||||||
private static File mTempDirectory;
|
|
||||||
|
|
||||||
private File mFile;
|
|
||||||
|
|
||||||
public static void setTempDirectory(File tempDirectory) {
|
|
||||||
mTempDirectory = tempDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BinaryTempFileBody() throws IOException {
|
|
||||||
if (mTempDirectory == null) {
|
|
||||||
throw new
|
|
||||||
RuntimeException("setTempDirectory has not been called on BinaryTempFileBody!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputStream getOutputStream() throws IOException {
|
|
||||||
mFile = File.createTempFile("body", null, mTempDirectory);
|
|
||||||
mFile.deleteOnExit();
|
|
||||||
return new FileOutputStream(mFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getInputStream() throws MessagingException {
|
|
||||||
try {
|
|
||||||
return new BinaryTempFileBodyInputStream(new FileInputStream(mFile));
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
throw new MessagingException("Unable to open body", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
|
||||||
InputStream in = getInputStream();
|
|
||||||
Base64OutputStream base64Out = new Base64OutputStream(out);
|
|
||||||
IOUtils.copy(in, base64Out);
|
|
||||||
base64Out.close();
|
|
||||||
mFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
class BinaryTempFileBodyInputStream extends FilterInputStream {
|
|
||||||
public BinaryTempFileBodyInputStream(InputStream in) {
|
|
||||||
super(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
super.close();
|
|
||||||
mFile.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.internet;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Body;
|
|
||||||
import com.fsck.k9.mail.BodyPart;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO this is a close approximation of Message, need to update along with
|
|
||||||
* Message.
|
|
||||||
*/
|
|
||||||
public class MimeBodyPart extends BodyPart {
|
|
||||||
protected MimeHeader mHeader = new MimeHeader();
|
|
||||||
protected Body mBody;
|
|
||||||
protected int mSize;
|
|
||||||
|
|
||||||
public MimeBodyPart() throws MessagingException {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MimeBodyPart(Body body) throws MessagingException {
|
|
||||||
this(body, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MimeBodyPart(Body body, String mimeType) throws MessagingException {
|
|
||||||
if (mimeType != null) {
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
|
|
||||||
}
|
|
||||||
setBody(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getFirstHeader(String name) throws MessagingException {
|
|
||||||
return mHeader.getFirstHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addHeader(String name, String value) throws MessagingException {
|
|
||||||
mHeader.addHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String name, String value) throws MessagingException {
|
|
||||||
mHeader.setHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getHeader(String name) throws MessagingException {
|
|
||||||
return mHeader.getHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeHeader(String name) throws MessagingException {
|
|
||||||
mHeader.removeHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Body getBody() throws MessagingException {
|
|
||||||
return mBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBody(Body body) throws MessagingException {
|
|
||||||
this.mBody = body;
|
|
||||||
if (body instanceof com.fsck.k9.mail.Multipart) {
|
|
||||||
com.fsck.k9.mail.Multipart multipart = ((com.fsck.k9.mail.Multipart)body);
|
|
||||||
multipart.setParent(this);
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
|
||||||
}
|
|
||||||
else if (body instanceof TextBody) {
|
|
||||||
String contentType = String.format("%s;\n charset=utf-8", getMimeType());
|
|
||||||
String name = MimeUtility.getHeaderParameter(getContentType(), "name");
|
|
||||||
if (name != null) {
|
|
||||||
contentType += String.format(";\n name=\"%s\"", name);
|
|
||||||
}
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() throws MessagingException {
|
|
||||||
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
|
||||||
if (contentType == null) {
|
|
||||||
return "text/plain";
|
|
||||||
} else {
|
|
||||||
return contentType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisposition() throws MessagingException {
|
|
||||||
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
|
||||||
if (contentDisposition == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return contentDisposition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMimeType() throws MessagingException {
|
|
||||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
|
||||||
return getMimeType().equals(mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize() throws MessagingException {
|
|
||||||
return mSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the MimeMessage out in MIME format.
|
|
||||||
*/
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
|
||||||
mHeader.writeTo(out);
|
|
||||||
writer.write("\r\n");
|
|
||||||
writer.flush();
|
|
||||||
if (mBody != null) {
|
|
||||||
mBody.writeTo(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.internet;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
|
|
||||||
public class MimeHeader {
|
|
||||||
/**
|
|
||||||
* Application specific header that contains Store specific information about an attachment.
|
|
||||||
* In IMAP this contains the IMAP BODYSTRUCTURE part id so that the ImapStore can later
|
|
||||||
* retrieve the attachment at will from the server.
|
|
||||||
* The info is recorded from this header on LocalStore.appendMessages and is put back
|
|
||||||
* into the MIME data by LocalStore.fetch.
|
|
||||||
*/
|
|
||||||
public static final String HEADER_ANDROID_ATTACHMENT_STORE_DATA = "X-Android-Attachment-StoreData";
|
|
||||||
|
|
||||||
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
|
||||||
public static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
|
|
||||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fields that should be omitted when writing the header using writeTo()
|
|
||||||
*/
|
|
||||||
private static final String[] writeOmitFields = {
|
|
||||||
// HEADER_ANDROID_ATTACHMENT_DOWNLOADED,
|
|
||||||
// HEADER_ANDROID_ATTACHMENT_ID,
|
|
||||||
HEADER_ANDROID_ATTACHMENT_STORE_DATA
|
|
||||||
};
|
|
||||||
|
|
||||||
protected ArrayList<Field> mFields = new ArrayList<Field>();
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
mFields.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstHeader(String name) throws MessagingException {
|
|
||||||
String[] header = getHeader(name);
|
|
||||||
if (header == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return header[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addHeader(String name, String value) throws MessagingException {
|
|
||||||
mFields.add(new Field(name, MimeUtility.foldAndEncode(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String name, String value) throws MessagingException {
|
|
||||||
if (name == null || value == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeHeader(name);
|
|
||||||
addHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getHeader(String name) throws MessagingException {
|
|
||||||
ArrayList<String> values = new ArrayList<String>();
|
|
||||||
for (Field field : mFields) {
|
|
||||||
if (field.name.equalsIgnoreCase(name)) {
|
|
||||||
values.add(field.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (values.size() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return values.toArray(new String[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeHeader(String name) throws MessagingException {
|
|
||||||
ArrayList<Field> removeFields = new ArrayList<Field>();
|
|
||||||
for (Field field : mFields) {
|
|
||||||
if (field.name.equalsIgnoreCase(name)) {
|
|
||||||
removeFields.add(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mFields.removeAll(removeFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
|
||||||
for (Field field : mFields) {
|
|
||||||
if (!Utility.arrayContains(writeOmitFields, field.name)) {
|
|
||||||
writer.write(field.name + ": " + field.value + "\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
class Field {
|
|
||||||
String name;
|
|
||||||
|
|
||||||
String value;
|
|
||||||
|
|
||||||
public Field(String name, String value) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,424 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.internet;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
import org.apache.james.mime4j.BodyDescriptor;
|
|
||||||
import org.apache.james.mime4j.ContentHandler;
|
|
||||||
import org.apache.james.mime4j.EOLConvertingInputStream;
|
|
||||||
import org.apache.james.mime4j.MimeStreamParser;
|
|
||||||
import org.apache.james.mime4j.field.DateTimeField;
|
|
||||||
import org.apache.james.mime4j.field.Field;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Address;
|
|
||||||
import com.fsck.k9.mail.Body;
|
|
||||||
import com.fsck.k9.mail.BodyPart;
|
|
||||||
import com.fsck.k9.mail.Message;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Part;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of Message that stores all of it's metadata in RFC 822 and
|
|
||||||
* RFC 2045 style headers.
|
|
||||||
*/
|
|
||||||
public class MimeMessage extends Message {
|
|
||||||
protected MimeHeader mHeader = new MimeHeader();
|
|
||||||
protected Address[] mFrom;
|
|
||||||
protected Address[] mTo;
|
|
||||||
protected Address[] mCc;
|
|
||||||
protected Address[] mBcc;
|
|
||||||
protected Address[] mReplyTo;
|
|
||||||
protected Date mSentDate;
|
|
||||||
protected SimpleDateFormat mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
|
|
||||||
protected Body mBody;
|
|
||||||
protected int mSize;
|
|
||||||
|
|
||||||
public MimeMessage() {
|
|
||||||
/*
|
|
||||||
* Every new messages gets a Message-ID
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
setHeader("Message-ID", generateMessageId());
|
|
||||||
}
|
|
||||||
catch (MessagingException me) {
|
|
||||||
throw new RuntimeException("Unable to create MimeMessage", me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateMessageId() {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
sb.append("<");
|
|
||||||
for (int i = 0; i < 24; i++) {
|
|
||||||
sb.append(Integer.toString((int)(Math.random() * 35), 36));
|
|
||||||
}
|
|
||||||
sb.append(".");
|
|
||||||
sb.append(Long.toString(System.currentTimeMillis()));
|
|
||||||
sb.append("@email.android.com>");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
|
|
||||||
*
|
|
||||||
* @param in
|
|
||||||
* @throws IOException
|
|
||||||
* @throws MessagingException
|
|
||||||
*/
|
|
||||||
public MimeMessage(InputStream in) throws IOException, MessagingException {
|
|
||||||
parse(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void parse(InputStream in) throws IOException, MessagingException {
|
|
||||||
mHeader.clear();
|
|
||||||
mBody = null;
|
|
||||||
mBcc = null;
|
|
||||||
mTo = null;
|
|
||||||
mFrom = null;
|
|
||||||
mSentDate = null;
|
|
||||||
|
|
||||||
MimeStreamParser parser = new MimeStreamParser();
|
|
||||||
parser.setContentHandler(new MimeMessageBuilder());
|
|
||||||
parser.parse(new EOLConvertingInputStream(in));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getReceivedDate() throws MessagingException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getSentDate() throws MessagingException {
|
|
||||||
if (mSentDate == null) {
|
|
||||||
try {
|
|
||||||
DateTimeField field = (DateTimeField)Field.parse("Date: "
|
|
||||||
+ MimeUtility.unfoldAndDecode(getFirstHeader("Date")));
|
|
||||||
mSentDate = field.getDate();
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mSentDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSentDate(Date sentDate) throws MessagingException {
|
|
||||||
setHeader("Date", mDateFormat.format(sentDate));
|
|
||||||
this.mSentDate = sentDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() throws MessagingException {
|
|
||||||
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
|
||||||
if (contentType == null) {
|
|
||||||
return "text/plain";
|
|
||||||
} else {
|
|
||||||
return contentType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisposition() throws MessagingException {
|
|
||||||
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
|
||||||
if (contentDisposition == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return contentDisposition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMimeType() throws MessagingException {
|
|
||||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize() throws MessagingException {
|
|
||||||
return mSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of the given recipient type from this message. If no addresses are
|
|
||||||
* found the method returns an empty array.
|
|
||||||
*/
|
|
||||||
public Address[] getRecipients(RecipientType type) throws MessagingException {
|
|
||||||
if (type == RecipientType.TO) {
|
|
||||||
if (mTo == null) {
|
|
||||||
mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
|
|
||||||
}
|
|
||||||
return mTo;
|
|
||||||
} else if (type == RecipientType.CC) {
|
|
||||||
if (mCc == null) {
|
|
||||||
mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
|
|
||||||
}
|
|
||||||
return mCc;
|
|
||||||
} else if (type == RecipientType.BCC) {
|
|
||||||
if (mBcc == null) {
|
|
||||||
mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
|
|
||||||
}
|
|
||||||
return mBcc;
|
|
||||||
} else {
|
|
||||||
throw new MessagingException("Unrecognized recipient type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRecipients(RecipientType type, Address[] addresses) throws MessagingException {
|
|
||||||
if (type == RecipientType.TO) {
|
|
||||||
if (addresses == null || addresses.length == 0) {
|
|
||||||
removeHeader("To");
|
|
||||||
this.mTo = null;
|
|
||||||
} else {
|
|
||||||
setHeader("To", Address.toString(addresses));
|
|
||||||
this.mTo = addresses;
|
|
||||||
}
|
|
||||||
} else if (type == RecipientType.CC) {
|
|
||||||
if (addresses == null || addresses.length == 0) {
|
|
||||||
removeHeader("CC");
|
|
||||||
this.mCc = null;
|
|
||||||
} else {
|
|
||||||
setHeader("CC", Address.toString(addresses));
|
|
||||||
this.mCc = addresses;
|
|
||||||
}
|
|
||||||
} else if (type == RecipientType.BCC) {
|
|
||||||
if (addresses == null || addresses.length == 0) {
|
|
||||||
removeHeader("BCC");
|
|
||||||
this.mBcc = null;
|
|
||||||
} else {
|
|
||||||
setHeader("BCC", Address.toString(addresses));
|
|
||||||
this.mBcc = addresses;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new MessagingException("Unrecognized recipient type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the unfolded, decoded value of the Subject header.
|
|
||||||
*/
|
|
||||||
public String getSubject() throws MessagingException {
|
|
||||||
return MimeUtility.unfoldAndDecode(getFirstHeader("Subject"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubject(String subject) throws MessagingException {
|
|
||||||
setHeader("Subject", subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Address[] getFrom() throws MessagingException {
|
|
||||||
if (mFrom == null) {
|
|
||||||
String list = MimeUtility.unfold(getFirstHeader("From"));
|
|
||||||
if (list == null || list.length() == 0) {
|
|
||||||
list = MimeUtility.unfold(getFirstHeader("Sender"));
|
|
||||||
}
|
|
||||||
mFrom = Address.parse(list);
|
|
||||||
}
|
|
||||||
return mFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFrom(Address from) throws MessagingException {
|
|
||||||
if (from != null) {
|
|
||||||
setHeader("From", from.toString());
|
|
||||||
this.mFrom = new Address[] {
|
|
||||||
from
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.mFrom = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Address[] getReplyTo() throws MessagingException {
|
|
||||||
if (mReplyTo == null) {
|
|
||||||
mReplyTo = Address.parse(MimeUtility.unfold(getFirstHeader("Reply-to")));
|
|
||||||
}
|
|
||||||
return mReplyTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReplyTo(Address[] replyTo) throws MessagingException {
|
|
||||||
if (replyTo == null || replyTo.length == 0) {
|
|
||||||
removeHeader("Reply-to");
|
|
||||||
mReplyTo = null;
|
|
||||||
} else {
|
|
||||||
setHeader("Reply-to", Address.toString(replyTo));
|
|
||||||
mReplyTo = replyTo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveChanges() throws MessagingException {
|
|
||||||
throw new MessagingException("saveChanges not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Body getBody() throws MessagingException {
|
|
||||||
return mBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBody(Body body) throws MessagingException {
|
|
||||||
this.mBody = body;
|
|
||||||
if (body instanceof com.fsck.k9.mail.Multipart) {
|
|
||||||
com.fsck.k9.mail.Multipart multipart = ((com.fsck.k9.mail.Multipart)body);
|
|
||||||
multipart.setParent(this);
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
|
||||||
setHeader("MIME-Version", "1.0");
|
|
||||||
}
|
|
||||||
else if (body instanceof TextBody) {
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8",
|
|
||||||
getMimeType()));
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getFirstHeader(String name) throws MessagingException {
|
|
||||||
return mHeader.getFirstHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addHeader(String name, String value) throws MessagingException {
|
|
||||||
mHeader.addHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String name, String value) throws MessagingException {
|
|
||||||
mHeader.setHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getHeader(String name) throws MessagingException {
|
|
||||||
return mHeader.getHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeHeader(String name) throws MessagingException {
|
|
||||||
mHeader.removeHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
|
||||||
mHeader.writeTo(out);
|
|
||||||
writer.write("\r\n");
|
|
||||||
writer.flush();
|
|
||||||
if (mBody != null) {
|
|
||||||
mBody.writeTo(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getInputStream() throws MessagingException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MimeMessageBuilder implements ContentHandler {
|
|
||||||
private Stack stack = new Stack();
|
|
||||||
|
|
||||||
public MimeMessageBuilder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expect(Class c) {
|
|
||||||
if (!c.isInstance(stack.peek())) {
|
|
||||||
throw new IllegalStateException("Internal stack error: " + "Expected '"
|
|
||||||
+ c.getName() + "' found '" + stack.peek().getClass().getName() + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startMessage() {
|
|
||||||
if (stack.isEmpty()) {
|
|
||||||
stack.push(MimeMessage.this);
|
|
||||||
} else {
|
|
||||||
expect(Part.class);
|
|
||||||
try {
|
|
||||||
MimeMessage m = new MimeMessage();
|
|
||||||
((Part)stack.peek()).setBody(m);
|
|
||||||
stack.push(m);
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new Error(me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endMessage() {
|
|
||||||
expect(MimeMessage.class);
|
|
||||||
stack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startHeader() {
|
|
||||||
expect(Part.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void field(String fieldData) {
|
|
||||||
expect(Part.class);
|
|
||||||
try {
|
|
||||||
String[] tokens = fieldData.split(":", 2);
|
|
||||||
((Part)stack.peek()).addHeader(tokens[0], tokens[1].trim());
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new Error(me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endHeader() {
|
|
||||||
expect(Part.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startMultipart(BodyDescriptor bd) {
|
|
||||||
expect(Part.class);
|
|
||||||
|
|
||||||
Part e = (Part)stack.peek();
|
|
||||||
try {
|
|
||||||
MimeMultipart multiPart = new MimeMultipart(e.getContentType());
|
|
||||||
e.setBody(multiPart);
|
|
||||||
stack.push(multiPart);
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new Error(me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void body(BodyDescriptor bd, InputStream in) throws IOException {
|
|
||||||
expect(Part.class);
|
|
||||||
Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
|
|
||||||
try {
|
|
||||||
((Part)stack.peek()).setBody(body);
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new Error(me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endMultipart() {
|
|
||||||
stack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startBodyPart() {
|
|
||||||
expect(MimeMultipart.class);
|
|
||||||
|
|
||||||
try {
|
|
||||||
MimeBodyPart bodyPart = new MimeBodyPart();
|
|
||||||
((MimeMultipart)stack.peek()).addBodyPart(bodyPart);
|
|
||||||
stack.push(bodyPart);
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new Error(me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endBodyPart() {
|
|
||||||
expect(BodyPart.class);
|
|
||||||
stack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void epilogue(InputStream is) throws IOException {
|
|
||||||
expect(MimeMultipart.class);
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
int b;
|
|
||||||
while ((b = is.read()) != -1) {
|
|
||||||
sb.append((char)b);
|
|
||||||
}
|
|
||||||
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void preamble(InputStream is) throws IOException {
|
|
||||||
expect(MimeMultipart.class);
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
int b;
|
|
||||||
while ((b = is.read()) != -1) {
|
|
||||||
sb.append((char)b);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new Error(me);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void raw(InputStream is) throws IOException {
|
|
||||||
throw new UnsupportedOperationException("Not supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.internet;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.BodyPart;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Multipart;
|
|
||||||
|
|
||||||
public class MimeMultipart extends Multipart {
|
|
||||||
protected String mPreamble;
|
|
||||||
|
|
||||||
protected String mContentType;
|
|
||||||
|
|
||||||
protected String mBoundary;
|
|
||||||
|
|
||||||
protected String mSubType;
|
|
||||||
|
|
||||||
public MimeMultipart() throws MessagingException {
|
|
||||||
mBoundary = generateBoundary();
|
|
||||||
setSubType("mixed");
|
|
||||||
}
|
|
||||||
|
|
||||||
public MimeMultipart(String contentType) throws MessagingException {
|
|
||||||
this.mContentType = contentType;
|
|
||||||
try {
|
|
||||||
mSubType = MimeUtility.getHeaderParameter(contentType, null).split("/")[1];
|
|
||||||
mBoundary = MimeUtility.getHeaderParameter(contentType, "boundary");
|
|
||||||
if (mBoundary == null) {
|
|
||||||
throw new MessagingException("MultiPart does not contain boundary: " + contentType);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new MessagingException(
|
|
||||||
"Invalid MultiPart Content-Type; must contain subtype and boundary. ("
|
|
||||||
+ contentType + ")", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateBoundary() {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
sb.append("----");
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
sb.append(Integer.toString((int)(Math.random() * 35), 36));
|
|
||||||
}
|
|
||||||
return sb.toString().toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPreamble() throws MessagingException {
|
|
||||||
return mPreamble;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreamble(String preamble) throws MessagingException {
|
|
||||||
this.mPreamble = preamble;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() throws MessagingException {
|
|
||||||
return mContentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubType(String subType) throws MessagingException {
|
|
||||||
this.mSubType = subType;
|
|
||||||
mContentType = String.format("multipart/%s; boundary=\"%s\"", subType, mBoundary);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
|
||||||
|
|
||||||
if (mPreamble != null) {
|
|
||||||
writer.write(mPreamble + "\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mParts.size() == 0){
|
|
||||||
writer.write("--" + mBoundary + "\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0, count = mParts.size(); i < count; i++) {
|
|
||||||
BodyPart bodyPart = (BodyPart)mParts.get(i);
|
|
||||||
writer.write("--" + mBoundary + "\r\n");
|
|
||||||
writer.flush();
|
|
||||||
bodyPart.writeTo(out);
|
|
||||||
writer.write("\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.write("--" + mBoundary + "--\r\n");
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getInputStream() throws MessagingException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,304 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.internet;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.james.mime4j.decoder.Base64InputStream;
|
|
||||||
import org.apache.james.mime4j.decoder.DecoderUtil;
|
|
||||||
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
|
|
||||||
import org.apache.james.mime4j.util.CharsetUtil;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.mail.Body;
|
|
||||||
import com.fsck.k9.mail.BodyPart;
|
|
||||||
import com.fsck.k9.mail.Message;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Multipart;
|
|
||||||
import com.fsck.k9.mail.Part;
|
|
||||||
|
|
||||||
public class MimeUtility {
|
|
||||||
public static String unfold(String s) {
|
|
||||||
if (s == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return s.replaceAll("\r|\n", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String decode(String s) {
|
|
||||||
if (s == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return DecoderUtil.decodeEncodedWords(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String unfoldAndDecode(String s) {
|
|
||||||
return decode(unfold(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO implement proper foldAndEncode
|
|
||||||
public static String foldAndEncode(String s) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the named parameter of a header field. If name is null the first
|
|
||||||
* parameter is returned, or if there are no additional parameters in the
|
|
||||||
* field the entire field is returned. Otherwise the named parameter is
|
|
||||||
* searched for in a case insensitive fashion and returned. If the parameter
|
|
||||||
* cannot be found the method returns null.
|
|
||||||
*
|
|
||||||
* @param header
|
|
||||||
* @param name
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getHeaderParameter(String header, String name) {
|
|
||||||
if (header == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
header = header.replaceAll("\r|\n", "");
|
|
||||||
String[] parts = header.split(";");
|
|
||||||
if (name == null) {
|
|
||||||
return parts[0];
|
|
||||||
}
|
|
||||||
for (String part : parts) {
|
|
||||||
if (part.trim().toLowerCase().startsWith(name.toLowerCase())) {
|
|
||||||
String parameter = part.split("=", 2)[1].trim();
|
|
||||||
if (parameter.startsWith("\"") && parameter.endsWith("\"")) {
|
|
||||||
return parameter.substring(1, parameter.length() - 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return parameter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Part findFirstPartByMimeType(Part part, String mimeType)
|
|
||||||
throws MessagingException {
|
|
||||||
if (part.getBody() instanceof Multipart) {
|
|
||||||
Multipart multipart = (Multipart)part.getBody();
|
|
||||||
for (int i = 0, count = multipart.getCount(); i < count; i++) {
|
|
||||||
BodyPart bodyPart = multipart.getBodyPart(i);
|
|
||||||
Part ret = findFirstPartByMimeType(bodyPart, mimeType);
|
|
||||||
if (ret != null) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.getMimeType().equalsIgnoreCase(mimeType)) {
|
|
||||||
return part;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Part findPartByContentId(Part part, String contentId) throws Exception {
|
|
||||||
if (part.getBody() instanceof Multipart) {
|
|
||||||
Multipart multipart = (Multipart)part.getBody();
|
|
||||||
for (int i = 0, count = multipart.getCount(); i < count; i++) {
|
|
||||||
BodyPart bodyPart = multipart.getBodyPart(i);
|
|
||||||
Part ret = findPartByContentId(bodyPart, contentId);
|
|
||||||
if (ret != null) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String[] header = part.getHeader("Content-ID");
|
|
||||||
if (header != null) {
|
|
||||||
for (String s : header) {
|
|
||||||
if (s.equals(contentId)) {
|
|
||||||
return part;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the Part's body and returns a String based on any charset conversion that needed
|
|
||||||
* to be done.
|
|
||||||
* @param part
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public static String getTextFromPart(Part part) {
|
|
||||||
try {
|
|
||||||
if (part != null && part.getBody() != null) {
|
|
||||||
InputStream in = part.getBody().getInputStream();
|
|
||||||
String mimeType = part.getMimeType();
|
|
||||||
if (mimeType != null && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
|
|
||||||
/*
|
|
||||||
* Now we read the part into a buffer for further processing. Because
|
|
||||||
* the stream is now wrapped we'll remove any transfer encoding at this point.
|
|
||||||
*/
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
IOUtils.copy(in, out);
|
|
||||||
|
|
||||||
byte[] bytes = out.toByteArray();
|
|
||||||
in.close();
|
|
||||||
out.close();
|
|
||||||
|
|
||||||
String charset = getHeaderParameter(part.getContentType(), "charset");
|
|
||||||
/*
|
|
||||||
* We've got a text part, so let's see if it needs to be processed further.
|
|
||||||
*/
|
|
||||||
if (charset != null) {
|
|
||||||
/*
|
|
||||||
* See if there is conversion from the MIME charset to the Java one.
|
|
||||||
*/
|
|
||||||
charset = CharsetUtil.toJavaCharset(charset);
|
|
||||||
}
|
|
||||||
if (charset != null) {
|
|
||||||
/*
|
|
||||||
* We've got a charset encoding, so decode using it.
|
|
||||||
*/
|
|
||||||
return new String(bytes, 0, bytes.length, charset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/*
|
|
||||||
* No encoding, so use us-ascii, which is the standard.
|
|
||||||
*/
|
|
||||||
return new String(bytes, 0, bytes.length, "ASCII");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
/*
|
|
||||||
* If we are not able to process the body there's nothing we can do about it. Return
|
|
||||||
* null and let the upper layers handle the missing content.
|
|
||||||
*/
|
|
||||||
Log.e(k9.LOG_TAG, "Unable to getTextFromPart", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given mimeType matches the matchAgainst specification.
|
|
||||||
* @param mimeType A MIME type to check.
|
|
||||||
* @param matchAgainst A MIME type to check against. May include wildcards such as image/* or
|
|
||||||
* * /*.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
|
|
||||||
return mimeType.matches(matchAgainst.replaceAll("\\*", "\\.\\*"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given mimeType matches any of the matchAgainst specifications.
|
|
||||||
* @param mimeType A MIME type to check.
|
|
||||||
* @param matchAgainst An array of MIME types to check against. May include wildcards such
|
|
||||||
* as image/* or * /*.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
|
|
||||||
for (String matchType : matchAgainst) {
|
|
||||||
if (mimeType.matches(matchType.replaceAll("\\*", "\\.\\*"))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes any content transfer encoding from the stream and returns a Body.
|
|
||||||
*/
|
|
||||||
public static Body decodeBody(InputStream in, String contentTransferEncoding)
|
|
||||||
throws IOException {
|
|
||||||
/*
|
|
||||||
* We'll remove any transfer encoding by wrapping the stream.
|
|
||||||
*/
|
|
||||||
if (contentTransferEncoding != null) {
|
|
||||||
contentTransferEncoding =
|
|
||||||
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
|
|
||||||
if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) {
|
|
||||||
in = new QuotedPrintableInputStream(in);
|
|
||||||
}
|
|
||||||
else if ("base64".equalsIgnoreCase(contentTransferEncoding)) {
|
|
||||||
in = new Base64InputStream(in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BinaryTempFileBody tempBody = new BinaryTempFileBody();
|
|
||||||
OutputStream out = tempBody.getOutputStream();
|
|
||||||
IOUtils.copy(in, out);
|
|
||||||
out.close();
|
|
||||||
return tempBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An unfortunately named method that makes decisions about a Part (usually a Message)
|
|
||||||
* as to which of it's children will be "viewable" and which will be attachments.
|
|
||||||
* The method recursively sorts the viewables and attachments into seperate
|
|
||||||
* lists for further processing.
|
|
||||||
* @param part
|
|
||||||
* @param viewables
|
|
||||||
* @param attachments
|
|
||||||
* @throws MessagingException
|
|
||||||
*/
|
|
||||||
public static void collectParts(Part part, ArrayList<Part> viewables,
|
|
||||||
ArrayList<Part> attachments) throws MessagingException {
|
|
||||||
String disposition = part.getDisposition();
|
|
||||||
String dispositionType = null;
|
|
||||||
String dispositionFilename = null;
|
|
||||||
if (disposition != null) {
|
|
||||||
dispositionType = MimeUtility.getHeaderParameter(disposition, null);
|
|
||||||
dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A best guess that this part is intended to be an attachment and not inline.
|
|
||||||
*/
|
|
||||||
boolean attachment = ("attachment".equalsIgnoreCase(dispositionType))
|
|
||||||
|| (dispositionFilename != null)
|
|
||||||
&& (!"inline".equalsIgnoreCase(dispositionType));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the part is Multipart but not alternative it's either mixed or
|
|
||||||
* something we don't know about, which means we treat it as mixed
|
|
||||||
* per the spec. We just process it's pieces recursively.
|
|
||||||
*/
|
|
||||||
if (part.getBody() instanceof Multipart) {
|
|
||||||
Multipart mp = (Multipart)part.getBody();
|
|
||||||
for (int i = 0; i < mp.getCount(); i++) {
|
|
||||||
collectParts(mp.getBodyPart(i), viewables, attachments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* If the part is an embedded message we just continue to process
|
|
||||||
* it, pulling any viewables or attachments into the running list.
|
|
||||||
*/
|
|
||||||
else if (part.getBody() instanceof Message) {
|
|
||||||
Message message = (Message)part.getBody();
|
|
||||||
collectParts(message, viewables, attachments);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* If the part is HTML and it got this far it's part of a mixed (et
|
|
||||||
* al) and should be rendered inline.
|
|
||||||
*/
|
|
||||||
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
|
|
||||||
viewables.add(part);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* If the part is plain text and it got this far it's part of a
|
|
||||||
* mixed (et al) and should be rendered inline.
|
|
||||||
*/
|
|
||||||
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
|
|
||||||
viewables.add(part);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Finally, if it's nothing else we will include it as an attachment.
|
|
||||||
*/
|
|
||||||
else {
|
|
||||||
attachments.add(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.internet;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
|
|
||||||
import com.fsck.k9.codec.binary.Base64;
|
|
||||||
import com.fsck.k9.mail.Body;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
|
|
||||||
public class TextBody implements Body {
|
|
||||||
String mBody;
|
|
||||||
|
|
||||||
public TextBody(String body) {
|
|
||||||
this.mBody = body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
|
||||||
byte[] bytes = mBody.getBytes("UTF-8");
|
|
||||||
out.write(Base64.encodeBase64Chunked(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the text of the body in it's unencoded format.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getText() {
|
|
||||||
return mBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an InputStream that reads this body's text in UTF-8 format.
|
|
||||||
*/
|
|
||||||
public InputStream getInputStream() throws MessagingException {
|
|
||||||
try {
|
|
||||||
byte[] b = mBody.getBytes("UTF-8");
|
|
||||||
return new ByteArrayInputStream(b);
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException usee) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,356 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.fsck.k9.mail.store;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.FixedLengthInputStream;
|
|
||||||
import com.fsck.k9.PeekableInputStream;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
|
|
||||||
public class ImapResponseParser {
|
|
||||||
SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z");
|
|
||||||
PeekableInputStream mIn;
|
|
||||||
InputStream mActiveLiteral;
|
|
||||||
|
|
||||||
public ImapResponseParser(PeekableInputStream in) {
|
|
||||||
this.mIn = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the next response available on the stream and returns an
|
|
||||||
* ImapResponse object that represents it.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public ImapResponse readResponse() throws IOException {
|
|
||||||
ImapResponse response = new ImapResponse();
|
|
||||||
if (mActiveLiteral != null) {
|
|
||||||
while (mActiveLiteral.read() != -1)
|
|
||||||
;
|
|
||||||
mActiveLiteral = null;
|
|
||||||
}
|
|
||||||
int ch = mIn.peek();
|
|
||||||
if (ch == '*') {
|
|
||||||
parseUntaggedResponse();
|
|
||||||
readTokens(response);
|
|
||||||
} else if (ch == '+') {
|
|
||||||
response.mCommandContinuationRequested =
|
|
||||||
parseCommandContinuationRequest();
|
|
||||||
readTokens(response);
|
|
||||||
} else {
|
|
||||||
response.mTag = parseTaggedResponse();
|
|
||||||
readTokens(response);
|
|
||||||
}
|
|
||||||
if (Config.LOGD) {
|
|
||||||
if (k9.DEBUG) {
|
|
||||||
Log.d(k9.LOG_TAG, "<<< " + response.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readTokens(ImapResponse response) throws IOException {
|
|
||||||
response.clear();
|
|
||||||
Object token;
|
|
||||||
while ((token = readToken()) != null) {
|
|
||||||
if (response != null) {
|
|
||||||
response.add(token);
|
|
||||||
}
|
|
||||||
if (mActiveLiteral != null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response.mCompleted = token == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the next token of the response. The token can be one of: String -
|
|
||||||
* for NIL, QUOTED, NUMBER, ATOM. InputStream - for LITERAL.
|
|
||||||
* InputStream.available() returns the total length of the stream.
|
|
||||||
* ImapResponseList - for PARENTHESIZED LIST. Can contain any of the above
|
|
||||||
* elements including List.
|
|
||||||
*
|
|
||||||
* @return The next token in the response or null if there are no more
|
|
||||||
* tokens.
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public Object readToken() throws IOException {
|
|
||||||
while (true) {
|
|
||||||
Object token = parseToken();
|
|
||||||
if (token == null || !token.equals(")")) {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object parseToken() throws IOException {
|
|
||||||
if (mActiveLiteral != null) {
|
|
||||||
while (mActiveLiteral.read() != -1)
|
|
||||||
;
|
|
||||||
mActiveLiteral = null;
|
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
int ch = mIn.peek();
|
|
||||||
if (ch == '(') {
|
|
||||||
return parseList();
|
|
||||||
} else if (ch == ')') {
|
|
||||||
expect(')');
|
|
||||||
return ")";
|
|
||||||
} else if (ch == '"') {
|
|
||||||
return parseQuoted();
|
|
||||||
} else if (ch == '{') {
|
|
||||||
mActiveLiteral = parseLiteral();
|
|
||||||
return mActiveLiteral;
|
|
||||||
} else if (ch == ' ') {
|
|
||||||
expect(' ');
|
|
||||||
} else if (ch == '\r') {
|
|
||||||
expect('\r');
|
|
||||||
expect('\n');
|
|
||||||
return null;
|
|
||||||
} else if (ch == '\n') {
|
|
||||||
expect('\n');
|
|
||||||
return null;
|
|
||||||
} else if (ch == '\t') {
|
|
||||||
expect('\t');
|
|
||||||
} else {
|
|
||||||
return parseAtom();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean parseCommandContinuationRequest() throws IOException {
|
|
||||||
expect('+');
|
|
||||||
expect(' ');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// * OK [UIDNEXT 175] Predicted next UID
|
|
||||||
private void parseUntaggedResponse() throws IOException {
|
|
||||||
expect('*');
|
|
||||||
expect(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3 OK [READ-WRITE] Select completed.
|
|
||||||
private String parseTaggedResponse() throws IOException {
|
|
||||||
String tag = readStringUntil(' ');
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImapList parseList() throws IOException {
|
|
||||||
expect('(');
|
|
||||||
ImapList list = new ImapList();
|
|
||||||
Object token;
|
|
||||||
while (true) {
|
|
||||||
token = parseToken();
|
|
||||||
if (token == null) {
|
|
||||||
break;
|
|
||||||
} else if (token instanceof InputStream) {
|
|
||||||
list.add(token);
|
|
||||||
break;
|
|
||||||
} else if (token.equals(")")) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
list.add(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseAtom() throws IOException {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
int ch;
|
|
||||||
while (true) {
|
|
||||||
ch = mIn.peek();
|
|
||||||
if (ch == -1) {
|
|
||||||
throw new IOException("parseAtom(): end of stream reached");
|
|
||||||
} else if (ch == '(' || ch == ')' || ch == '{' || ch == ' ' ||
|
|
||||||
// docs claim that flags are \ atom but atom isn't supposed to
|
|
||||||
// contain
|
|
||||||
// * and some falgs contain *
|
|
||||||
// ch == '%' || ch == '*' ||
|
|
||||||
ch == '%' ||
|
|
||||||
// TODO probably should not allow \ and should recognize
|
|
||||||
// it as a flag instead
|
|
||||||
// ch == '"' || ch == '\' ||
|
|
||||||
ch == '"' || (ch >= 0x00 && ch <= 0x1f) || ch == 0x7f) {
|
|
||||||
if (sb.length() == 0) {
|
|
||||||
throw new IOException(String.format("parseAtom(): (%04x %c)", (int)ch, ch));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
} else {
|
|
||||||
sb.append((char)mIn.read());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A { has been read, read the rest of the size string, the space and then
|
|
||||||
* notify the listener with an InputStream.
|
|
||||||
*
|
|
||||||
* @param mListener
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private InputStream parseLiteral() throws IOException {
|
|
||||||
expect('{');
|
|
||||||
int size = Integer.parseInt(readStringUntil('}'));
|
|
||||||
expect('\r');
|
|
||||||
expect('\n');
|
|
||||||
FixedLengthInputStream fixed = new FixedLengthInputStream(mIn, size);
|
|
||||||
return fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A " has been read, read to the end of the quoted string and notify the
|
|
||||||
* listener.
|
|
||||||
*
|
|
||||||
* @param mListener
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private String parseQuoted() throws IOException {
|
|
||||||
expect('"');
|
|
||||||
return readStringUntil('"');
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readStringUntil(char end) throws IOException {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
int ch;
|
|
||||||
while ((ch = mIn.read()) != -1) {
|
|
||||||
if (ch == end) {
|
|
||||||
return sb.toString();
|
|
||||||
} else {
|
|
||||||
sb.append((char)ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IOException("readQuotedString(): end of stream reached");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int expect(char ch) throws IOException {
|
|
||||||
int d;
|
|
||||||
if ((d = mIn.read()) != ch) {
|
|
||||||
throw new IOException(String.format("Expected %04x (%c) but got %04x (%c)", (int)ch,
|
|
||||||
ch, d, (char)d));
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an IMAP LIST response and is also the base class for the
|
|
||||||
* ImapResponse.
|
|
||||||
*/
|
|
||||||
public class ImapList extends ArrayList<Object> {
|
|
||||||
public ImapList getList(int index) {
|
|
||||||
return (ImapList)get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getString(int index) {
|
|
||||||
return (String)get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getLiteral(int index) {
|
|
||||||
return (InputStream)get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNumber(int index) {
|
|
||||||
return Integer.parseInt(getString(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getDate(int index) throws MessagingException {
|
|
||||||
try {
|
|
||||||
return mDateTimeFormat.parse(getString(index));
|
|
||||||
} catch (ParseException pe) {
|
|
||||||
throw new MessagingException("Unable to parse IMAP datetime", pe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getKeyedValue(Object key) {
|
|
||||||
for (int i = 0, count = size(); i < count; i++) {
|
|
||||||
if (get(i).equals(key)) {
|
|
||||||
return get(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImapList getKeyedList(Object key) {
|
|
||||||
return (ImapList)getKeyedValue(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyedString(Object key) {
|
|
||||||
return (String)getKeyedValue(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getKeyedLiteral(Object key) {
|
|
||||||
return (InputStream)getKeyedValue(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKeyedNumber(Object key) {
|
|
||||||
return Integer.parseInt(getKeyedString(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getKeyedDate(Object key) throws MessagingException {
|
|
||||||
try {
|
|
||||||
String value = getKeyedString(key);
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return mDateTimeFormat.parse(value);
|
|
||||||
} catch (ParseException pe) {
|
|
||||||
throw new MessagingException("Unable to parse IMAP datetime", pe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a single response from the IMAP server. Tagged responses will
|
|
||||||
* have a non-null tag. Untagged responses will have a null tag. The object
|
|
||||||
* will contain all of the available tokens at the time the response is
|
|
||||||
* received. In general, it will either contain all of the tokens of the
|
|
||||||
* response or all of the tokens up until the first LITERAL. If the object
|
|
||||||
* does not contain the entire response the caller must call more() to
|
|
||||||
* continue reading the response until more returns false.
|
|
||||||
*/
|
|
||||||
public class ImapResponse extends ImapList {
|
|
||||||
private boolean mCompleted;
|
|
||||||
|
|
||||||
boolean mCommandContinuationRequested;
|
|
||||||
String mTag;
|
|
||||||
|
|
||||||
public boolean more() throws IOException {
|
|
||||||
if (mCompleted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
readTokens(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAlertText() {
|
|
||||||
if (size() > 1 && "[ALERT]".equals(getString(1))) {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
for (int i = 2, count = size(); i < count; i++) {
|
|
||||||
sb.append(get(i).toString());
|
|
||||||
sb.append(' ');
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "#" + mTag + "# " + super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,880 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.store;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.SSLException;
|
|
||||||
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
import com.fsck.k9.mail.AuthenticationFailedException;
|
|
||||||
import com.fsck.k9.mail.FetchProfile;
|
|
||||||
import com.fsck.k9.mail.Flag;
|
|
||||||
import com.fsck.k9.mail.Folder;
|
|
||||||
import com.fsck.k9.mail.Message;
|
|
||||||
import com.fsck.k9.mail.MessageRetrievalListener;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Store;
|
|
||||||
import com.fsck.k9.mail.CertificateValidationException;
|
|
||||||
import com.fsck.k9.mail.Folder.OpenMode;
|
|
||||||
import com.fsck.k9.mail.internet.MimeMessage;
|
|
||||||
|
|
||||||
public class Pop3Store extends Store {
|
|
||||||
public static final int CONNECTION_SECURITY_NONE = 0;
|
|
||||||
public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
|
|
||||||
public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
|
|
||||||
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
|
|
||||||
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
|
|
||||||
|
|
||||||
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED };
|
|
||||||
|
|
||||||
private String mHost;
|
|
||||||
private int mPort;
|
|
||||||
private String mUsername;
|
|
||||||
private String mPassword;
|
|
||||||
private int mConnectionSecurity;
|
|
||||||
private HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
|
|
||||||
private Pop3Capabilities mCapabilities;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Detected latency, used for usage scaling.
|
|
||||||
// * Usage scaling occurs when it is neccesary to get information about
|
|
||||||
// * messages that could result in large data loads. This value allows
|
|
||||||
// * the code that loads this data to decide between using large downloads
|
|
||||||
// * (high latency) or multiple round trips (low latency) to accomplish
|
|
||||||
// * the same thing.
|
|
||||||
// * Default is Integer.MAX_VALUE implying massive latency so that the large
|
|
||||||
// * download method is used by default until latency data is collected.
|
|
||||||
// */
|
|
||||||
// private int mLatencyMs = Integer.MAX_VALUE;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Detected throughput, used for usage scaling.
|
|
||||||
// * Usage scaling occurs when it is neccesary to get information about
|
|
||||||
// * messages that could result in large data loads. This value allows
|
|
||||||
// * the code that loads this data to decide between using large downloads
|
|
||||||
// * (high latency) or multiple round trips (low latency) to accomplish
|
|
||||||
// * the same thing.
|
|
||||||
// * Default is Integer.MAX_VALUE implying massive bandwidth so that the
|
|
||||||
// * large download method is used by default until latency data is
|
|
||||||
// * collected.
|
|
||||||
// */
|
|
||||||
// private int mThroughputKbS = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pop3://user:password@server:port CONNECTION_SECURITY_NONE
|
|
||||||
* pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
|
|
||||||
* pop3+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
|
|
||||||
* pop3+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
|
|
||||||
* pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
|
|
||||||
*
|
|
||||||
* @param _uri
|
|
||||||
*/
|
|
||||||
public Pop3Store(String _uri) throws MessagingException {
|
|
||||||
URI uri;
|
|
||||||
try {
|
|
||||||
uri = new URI(_uri);
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
throw new MessagingException("Invalid Pop3Store URI", use);
|
|
||||||
}
|
|
||||||
|
|
||||||
String scheme = uri.getScheme();
|
|
||||||
if (scheme.equals("pop3")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_NONE;
|
|
||||||
mPort = 110;
|
|
||||||
} else if (scheme.equals("pop3+tls")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL;
|
|
||||||
mPort = 110;
|
|
||||||
} else if (scheme.equals("pop3+tls+")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED;
|
|
||||||
mPort = 110;
|
|
||||||
} else if (scheme.equals("pop3+ssl+")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
|
|
||||||
mPort = 995;
|
|
||||||
} else if (scheme.equals("pop3+ssl")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL;
|
|
||||||
mPort = 995;
|
|
||||||
} else {
|
|
||||||
throw new MessagingException("Unsupported protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
mHost = uri.getHost();
|
|
||||||
|
|
||||||
if (uri.getPort() != -1) {
|
|
||||||
mPort = uri.getPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.getUserInfo() != null) {
|
|
||||||
String[] userInfoParts = uri.getUserInfo().split(":", 2);
|
|
||||||
mUsername = userInfoParts[0];
|
|
||||||
if (userInfoParts.length > 1) {
|
|
||||||
mPassword = userInfoParts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Folder getFolder(String name) throws MessagingException {
|
|
||||||
Folder folder = mFolders.get(name);
|
|
||||||
if (folder == null) {
|
|
||||||
folder = new Pop3Folder(name);
|
|
||||||
mFolders.put(folder.getName(), folder);
|
|
||||||
}
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Folder[] getPersonalNamespaces() throws MessagingException {
|
|
||||||
return new Folder[] {
|
|
||||||
getFolder("INBOX"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkSettings() throws MessagingException {
|
|
||||||
Pop3Folder folder = new Pop3Folder("INBOX");
|
|
||||||
folder.open(OpenMode.READ_WRITE);
|
|
||||||
if (!mCapabilities.uidl) {
|
|
||||||
/*
|
|
||||||
* Run an additional test to see if UIDL is supported on the server. If it's not we
|
|
||||||
* can't service this account.
|
|
||||||
*/
|
|
||||||
try{
|
|
||||||
/*
|
|
||||||
* If the server doesn't support UIDL it will return a - response, which causes
|
|
||||||
* executeSimpleCommand to throw a MessagingException, exiting this method.
|
|
||||||
*/
|
|
||||||
folder.executeSimpleCommand("UIDL");
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
throw new MessagingException(null, ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
folder.close(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pop3Folder extends Folder {
|
|
||||||
private Socket mSocket;
|
|
||||||
private InputStream mIn;
|
|
||||||
private OutputStream mOut;
|
|
||||||
private HashMap<String, Pop3Message> mUidToMsgMap = new HashMap<String, Pop3Message>();
|
|
||||||
private HashMap<Integer, Pop3Message> mMsgNumToMsgMap = new HashMap<Integer, Pop3Message>();
|
|
||||||
private HashMap<String, Integer> mUidToMsgNumMap = new HashMap<String, Integer>();
|
|
||||||
private String mName;
|
|
||||||
private int mMessageCount;
|
|
||||||
|
|
||||||
public Pop3Folder(String name) {
|
|
||||||
this.mName = name;
|
|
||||||
if (mName.equalsIgnoreCase("INBOX")) {
|
|
||||||
mName = "INBOX";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void open(OpenMode mode) throws MessagingException {
|
|
||||||
if (isOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mName.equalsIgnoreCase("INBOX")) {
|
|
||||||
throw new MessagingException("Folder does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
|
|
||||||
if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
|
|
||||||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
||||||
final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
|
|
||||||
sslContext.init(null, new TrustManager[] {
|
|
||||||
TrustManagerFactory.get(mHost, secure)
|
|
||||||
}, new SecureRandom());
|
|
||||||
mSocket = sslContext.getSocketFactory().createSocket();
|
|
||||||
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
|
|
||||||
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
|
|
||||||
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
|
|
||||||
} else {
|
|
||||||
mSocket = new Socket();
|
|
||||||
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
|
|
||||||
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
|
|
||||||
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
|
|
||||||
}
|
|
||||||
|
|
||||||
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
|
|
||||||
|
|
||||||
|
|
||||||
// Eat the banner
|
|
||||||
executeSimpleCommand(null);
|
|
||||||
|
|
||||||
mCapabilities = getCapabilities();
|
|
||||||
|
|
||||||
if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL
|
|
||||||
|| mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
|
|
||||||
if (mCapabilities.stls) {
|
|
||||||
writeLine("STLS");
|
|
||||||
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
||||||
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
|
|
||||||
sslContext.init(null, new TrustManager[] {
|
|
||||||
TrustManagerFactory.get(mHost, secure)
|
|
||||||
}, new SecureRandom());
|
|
||||||
mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort,
|
|
||||||
true);
|
|
||||||
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
|
|
||||||
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
|
|
||||||
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
|
|
||||||
} else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
|
|
||||||
throw new MessagingException("TLS not supported but required");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
executeSimpleCommand("USER " + mUsername);
|
|
||||||
executeSimpleCommand("PASS " + mPassword);
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new AuthenticationFailedException(null, me);
|
|
||||||
}
|
|
||||||
} catch (SSLException e) {
|
|
||||||
throw new CertificateValidationException(e.getMessage(), e);
|
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
throw new MessagingException(
|
|
||||||
"Unable to open connection to POP server due to security error.", gse);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new MessagingException("Unable to open connection to POP server.", ioe);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String response = executeSimpleCommand("STAT");
|
|
||||||
String[] parts = response.split(" ");
|
|
||||||
mMessageCount = Integer.parseInt(parts[1]);
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
throw new MessagingException("Unable to STAT folder.", ioe);
|
|
||||||
}
|
|
||||||
mUidToMsgMap.clear();
|
|
||||||
mMsgNumToMsgMap.clear();
|
|
||||||
mUidToMsgNumMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOpen() {
|
|
||||||
return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket
|
|
||||||
.isClosed());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OpenMode getMode() throws MessagingException {
|
|
||||||
return OpenMode.READ_ONLY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close(boolean expunge) {
|
|
||||||
try {
|
|
||||||
executeSimpleCommand("QUIT");
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
/*
|
|
||||||
* QUIT may fail if the connection is already closed. We don't care. It's just
|
|
||||||
* being friendly.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
mIn.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
/*
|
|
||||||
* May fail if the connection is already closed.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
mOut.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
/*
|
|
||||||
* May fail if the connection is already closed.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
mSocket.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
/*
|
|
||||||
* May fail if the connection is already closed.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
mIn = null;
|
|
||||||
mOut = null;
|
|
||||||
mSocket = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean create(FolderType type) throws MessagingException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean exists() throws MessagingException {
|
|
||||||
return mName.equalsIgnoreCase("INBOX");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMessageCount() {
|
|
||||||
return mMessageCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getUnreadMessageCount() throws MessagingException {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message getMessage(String uid) throws MessagingException {
|
|
||||||
Pop3Message message = mUidToMsgMap.get(uid);
|
|
||||||
if (message == null) {
|
|
||||||
message = new Pop3Message(uid, this);
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
|
||||||
throws MessagingException {
|
|
||||||
if (start < 1 || end < 1 || end < start) {
|
|
||||||
throw new MessagingException(String.format("Invalid message set %d %d",
|
|
||||||
start, end));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
indexMsgNums(start, end);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new MessagingException("getMessages", ioe);
|
|
||||||
}
|
|
||||||
ArrayList<Message> messages = new ArrayList<Message>();
|
|
||||||
int i = 0;
|
|
||||||
for (int msgNum = start; msgNum <= end; msgNum++) {
|
|
||||||
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
|
|
||||||
if (listener != null) {
|
|
||||||
listener.messageStarted(message.getUid(), i++, (end - start) + 1);
|
|
||||||
}
|
|
||||||
messages.add(message);
|
|
||||||
if (listener != null) {
|
|
||||||
listener.messageFinished(message, i++, (end - start) + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return messages.toArray(new Message[messages.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the given message set (from start to end inclusive)
|
|
||||||
* has been queried so that uids are available in the local cache.
|
|
||||||
* @param start
|
|
||||||
* @param end
|
|
||||||
* @throws MessagingException
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private void indexMsgNums(int start, int end)
|
|
||||||
throws MessagingException, IOException {
|
|
||||||
int unindexedMessageCount = 0;
|
|
||||||
for (int msgNum = start; msgNum <= end; msgNum++) {
|
|
||||||
if (mMsgNumToMsgMap.get(msgNum) == null) {
|
|
||||||
unindexedMessageCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (unindexedMessageCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (unindexedMessageCount < 50 && mMessageCount > 5000) {
|
|
||||||
/*
|
|
||||||
* In extreme cases we'll do a UIDL command per message instead of a bulk
|
|
||||||
* download.
|
|
||||||
*/
|
|
||||||
for (int msgNum = start; msgNum <= end; msgNum++) {
|
|
||||||
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
|
|
||||||
if (message == null) {
|
|
||||||
String response = executeSimpleCommand("UIDL " + msgNum);
|
|
||||||
int uidIndex = response.lastIndexOf(' ');
|
|
||||||
String msgUid = response.substring(uidIndex + 1);
|
|
||||||
message = new Pop3Message(msgUid, this);
|
|
||||||
indexMessage(msgNum, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
String response = executeSimpleCommand("UIDL");
|
|
||||||
while ((response = readLine()) != null) {
|
|
||||||
if (response.equals(".")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String[] uidParts = response.split(" ");
|
|
||||||
Integer msgNum = Integer.valueOf(uidParts[0]);
|
|
||||||
String msgUid = uidParts[1];
|
|
||||||
if (msgNum >= start && msgNum <= end) {
|
|
||||||
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
|
|
||||||
if (message == null) {
|
|
||||||
message = new Pop3Message(msgUid, this);
|
|
||||||
indexMessage(msgNum, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void indexUids(ArrayList<String> uids)
|
|
||||||
throws MessagingException, IOException {
|
|
||||||
HashSet<String> unindexedUids = new HashSet<String>();
|
|
||||||
for (String uid : uids) {
|
|
||||||
if (mUidToMsgMap.get(uid) == null) {
|
|
||||||
unindexedUids.add(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (unindexedUids.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* If we are missing uids in the cache the only sure way to
|
|
||||||
* get them is to do a full UIDL list. A possible optimization
|
|
||||||
* would be trying UIDL for the latest X messages and praying.
|
|
||||||
*/
|
|
||||||
String response = executeSimpleCommand("UIDL");
|
|
||||||
while ((response = readLine()) != null) {
|
|
||||||
if (response.equals(".")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String[] uidParts = response.split(" ");
|
|
||||||
Integer msgNum = Integer.valueOf(uidParts[0]);
|
|
||||||
String msgUid = uidParts[1];
|
|
||||||
if (unindexedUids.contains(msgUid)) {
|
|
||||||
if (Config.LOGD) {
|
|
||||||
Pop3Message message = mUidToMsgMap.get(msgUid);
|
|
||||||
if (message == null) {
|
|
||||||
message = new Pop3Message(msgUid, this);
|
|
||||||
}
|
|
||||||
indexMessage(msgNum, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void indexMessage(int msgNum, Pop3Message message) {
|
|
||||||
mMsgNumToMsgMap.put(msgNum, message);
|
|
||||||
mUidToMsgMap.put(message.getUid(), message);
|
|
||||||
mUidToMsgNumMap.put(message.getUid(), msgNum);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
|
|
||||||
throw new UnsupportedOperationException("Pop3Folder.getMessage(MessageRetrievalListener)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
|
||||||
throws MessagingException {
|
|
||||||
throw new UnsupportedOperationException("Pop3Folder.getMessage(MessageRetrievalListener)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the items contained in the FetchProfile into the given set of
|
|
||||||
* Messages in as efficient a manner as possible.
|
|
||||||
* @param messages
|
|
||||||
* @param fp
|
|
||||||
* @throws MessagingException
|
|
||||||
*/
|
|
||||||
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
|
|
||||||
throws MessagingException {
|
|
||||||
if (messages == null || messages.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ArrayList<String> uids = new ArrayList<String>();
|
|
||||||
for (Message message : messages) {
|
|
||||||
uids.add(message.getUid());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
indexUids(uids);
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
throw new MessagingException("fetch", ioe);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
|
|
||||||
/*
|
|
||||||
* We pass the listener only if there are other things to do in the
|
|
||||||
* FetchProfile. Since fetchEnvelop works in bulk and eveything else
|
|
||||||
* works one at a time if we let fetchEnvelope send events the
|
|
||||||
* event would get sent twice.
|
|
||||||
*/
|
|
||||||
fetchEnvelope(messages, fp.size() == 1 ? listener : null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
throw new MessagingException("fetch", ioe);
|
|
||||||
}
|
|
||||||
for (int i = 0, count = messages.length; i < count; i++) {
|
|
||||||
Message message = messages[i];
|
|
||||||
if (!(message instanceof Pop3Message)) {
|
|
||||||
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
|
|
||||||
}
|
|
||||||
Pop3Message pop3Message = (Pop3Message)message;
|
|
||||||
try {
|
|
||||||
if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE)) {
|
|
||||||
listener.messageStarted(pop3Message.getUid(), i, count);
|
|
||||||
}
|
|
||||||
if (fp.contains(FetchProfile.Item.BODY)) {
|
|
||||||
fetchBody(pop3Message, -1);
|
|
||||||
}
|
|
||||||
else if (fp.contains(FetchProfile.Item.BODY_SANE)) {
|
|
||||||
/*
|
|
||||||
* To convert the suggested download size we take the size
|
|
||||||
* divided by the maximum line size (76).
|
|
||||||
*/
|
|
||||||
fetchBody(pop3Message,
|
|
||||||
FETCH_BODY_SANE_SUGGESTED_SIZE / 76);
|
|
||||||
}
|
|
||||||
else if (fp.contains(FetchProfile.Item.STRUCTURE)) {
|
|
||||||
/*
|
|
||||||
* If the user is requesting STRUCTURE we are required to set the body
|
|
||||||
* to null since we do not support the function.
|
|
||||||
*/
|
|
||||||
pop3Message.setBody(null);
|
|
||||||
}
|
|
||||||
if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE)) {
|
|
||||||
listener.messageFinished(message, i, count);
|
|
||||||
}
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new MessagingException("Unable to fetch message", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fetchEnvelope(Message[] messages,
|
|
||||||
MessageRetrievalListener listener) throws IOException, MessagingException {
|
|
||||||
int unsizedMessages = 0;
|
|
||||||
for (Message message : messages) {
|
|
||||||
if (message.getSize() == -1) {
|
|
||||||
unsizedMessages++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (unsizedMessages == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (unsizedMessages < 50 && mMessageCount > 5000) {
|
|
||||||
/*
|
|
||||||
* In extreme cases we'll do a command per message instead of a bulk request
|
|
||||||
* to hopefully save some time and bandwidth.
|
|
||||||
*/
|
|
||||||
for (int i = 0, count = messages.length; i < count; i++) {
|
|
||||||
Message message = messages[i];
|
|
||||||
if (!(message instanceof Pop3Message)) {
|
|
||||||
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
|
|
||||||
}
|
|
||||||
Pop3Message pop3Message = (Pop3Message)message;
|
|
||||||
if (listener != null) {
|
|
||||||
listener.messageStarted(pop3Message.getUid(), i, count);
|
|
||||||
}
|
|
||||||
String response = executeSimpleCommand(String.format("LIST %d",
|
|
||||||
mUidToMsgNumMap.get(pop3Message.getUid())));
|
|
||||||
String[] listParts = response.split(" ");
|
|
||||||
int msgNum = Integer.parseInt(listParts[1]);
|
|
||||||
int msgSize = Integer.parseInt(listParts[2]);
|
|
||||||
pop3Message.setSize(msgSize);
|
|
||||||
if (listener != null) {
|
|
||||||
listener.messageFinished(pop3Message, i, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
HashSet<String> msgUidIndex = new HashSet<String>();
|
|
||||||
for (Message message : messages) {
|
|
||||||
msgUidIndex.add(message.getUid());
|
|
||||||
}
|
|
||||||
int i = 0, count = messages.length;
|
|
||||||
String response = executeSimpleCommand("LIST");
|
|
||||||
while ((response = readLine()) != null) {
|
|
||||||
if (response.equals(".")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String[] listParts = response.split(" ");
|
|
||||||
int msgNum = Integer.parseInt(listParts[0]);
|
|
||||||
int msgSize = Integer.parseInt(listParts[1]);
|
|
||||||
Pop3Message pop3Message = mMsgNumToMsgMap.get(msgNum);
|
|
||||||
if (pop3Message != null && msgUidIndex.contains(pop3Message.getUid())) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.messageStarted(pop3Message.getUid(), i, count);
|
|
||||||
}
|
|
||||||
pop3Message.setSize(msgSize);
|
|
||||||
if (listener != null) {
|
|
||||||
listener.messageFinished(pop3Message, i, count);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the body of the given message, limiting the stored data
|
|
||||||
* to the specified number of lines. If lines is -1 the entire message
|
|
||||||
* is fetched. This is implemented with RETR for lines = -1 or TOP
|
|
||||||
* for any other value. If the server does not support TOP it is
|
|
||||||
* emulated with RETR and extra lines are thrown away.
|
|
||||||
* @param message
|
|
||||||
* @param lines
|
|
||||||
*/
|
|
||||||
private void fetchBody(Pop3Message message, int lines)
|
|
||||||
throws IOException, MessagingException {
|
|
||||||
String response = null;
|
|
||||||
if (lines == -1 || !mCapabilities.top) {
|
|
||||||
response = executeSimpleCommand(String.format("RETR %d",
|
|
||||||
mUidToMsgNumMap.get(message.getUid())));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
response = executeSimpleCommand(String.format("TOP %d %d",
|
|
||||||
mUidToMsgNumMap.get(message.getUid()),
|
|
||||||
lines));
|
|
||||||
}
|
|
||||||
if (response != null) {
|
|
||||||
try {
|
|
||||||
message.parse(new Pop3ResponseInputStream(mIn));
|
|
||||||
}
|
|
||||||
catch (MessagingException me) {
|
|
||||||
/*
|
|
||||||
* If we're only downloading headers it's possible
|
|
||||||
* we'll get a broken MIME message which we're not
|
|
||||||
* real worried about. If we've downloaded the body
|
|
||||||
* and can't parse it we need to let the user know.
|
|
||||||
*/
|
|
||||||
if (lines == -1) {
|
|
||||||
throw me;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flag[] getPermanentFlags() throws MessagingException {
|
|
||||||
return PERMANENT_FLAGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendMessages(Message[] messages) throws MessagingException {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(boolean recurse) throws MessagingException {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Message[] expunge() throws MessagingException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFlags(Message[] messages, Flag[] flags, boolean value)
|
|
||||||
throws MessagingException {
|
|
||||||
if (!value || !Utility.arrayContains(flags, Flag.DELETED)) {
|
|
||||||
/*
|
|
||||||
* The only flagging we support is setting the Deleted flag.
|
|
||||||
*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (Message message : messages) {
|
|
||||||
executeSimpleCommand(String.format("DELE %s",
|
|
||||||
mUidToMsgNumMap.get(message.getUid())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
throw new MessagingException("setFlags()", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {
|
|
||||||
throw new UnsupportedOperationException("copyMessages is not supported in POP3");
|
|
||||||
}
|
|
||||||
|
|
||||||
// private boolean isRoundTripModeSuggested() {
|
|
||||||
// long roundTripMethodMs =
|
|
||||||
// (uncachedMessageCount * 2 * mLatencyMs);
|
|
||||||
// long bulkMethodMs =
|
|
||||||
// (mMessageCount * 58) / (mThroughputKbS * 1024 / 8) * 1000;
|
|
||||||
// }
|
|
||||||
|
|
||||||
private String readLine() throws IOException {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
int d = mIn.read();
|
|
||||||
if (d == -1) {
|
|
||||||
throw new IOException("End of stream reached while trying to read line.");
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
if (((char)d) == '\r') {
|
|
||||||
continue;
|
|
||||||
} else if (((char)d) == '\n') {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
sb.append((char)d);
|
|
||||||
}
|
|
||||||
} while ((d = mIn.read()) != -1);
|
|
||||||
String ret = sb.toString();
|
|
||||||
if (Config.LOGD) {
|
|
||||||
if (k9.DEBUG) {
|
|
||||||
Log.d(k9.LOG_TAG, "<<< " + ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeLine(String s) throws IOException {
|
|
||||||
if (Config.LOGD) {
|
|
||||||
if (k9.DEBUG) {
|
|
||||||
Log.d(k9.LOG_TAG, ">>> " + s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mOut.write(s.getBytes());
|
|
||||||
mOut.write('\r');
|
|
||||||
mOut.write('\n');
|
|
||||||
mOut.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pop3Capabilities getCapabilities() throws IOException, MessagingException {
|
|
||||||
Pop3Capabilities capabilities = new Pop3Capabilities();
|
|
||||||
try {
|
|
||||||
String response = executeSimpleCommand("CAPA");
|
|
||||||
while ((response = readLine()) != null) {
|
|
||||||
if (response.equals(".")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (response.equalsIgnoreCase("STLS")){
|
|
||||||
capabilities.stls = true;
|
|
||||||
}
|
|
||||||
else if (response.equalsIgnoreCase("UIDL")) {
|
|
||||||
capabilities.uidl = true;
|
|
||||||
}
|
|
||||||
else if (response.equalsIgnoreCase("PIPELINING")) {
|
|
||||||
capabilities.pipelining = true;
|
|
||||||
}
|
|
||||||
else if (response.equalsIgnoreCase("USER")) {
|
|
||||||
capabilities.user = true;
|
|
||||||
}
|
|
||||||
else if (response.equalsIgnoreCase("TOP")) {
|
|
||||||
capabilities.top = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (MessagingException me) {
|
|
||||||
/*
|
|
||||||
* The server may not support the CAPA command, so we just eat this Exception
|
|
||||||
* and allow the empty capabilities object to be returned.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
return capabilities;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String executeSimpleCommand(String command) throws IOException, MessagingException {
|
|
||||||
open(OpenMode.READ_WRITE);
|
|
||||||
|
|
||||||
if (command != null) {
|
|
||||||
writeLine(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
String response = readLine();
|
|
||||||
|
|
||||||
if (response.length() > 1 && response.charAt(0) == '-') {
|
|
||||||
throw new MessagingException(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o instanceof Pop3Folder) {
|
|
||||||
return ((Pop3Folder) o).mName.equals(mName);
|
|
||||||
}
|
|
||||||
return super.equals(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pop3Message extends MimeMessage {
|
|
||||||
public Pop3Message(String uid, Pop3Folder folder) throws MessagingException {
|
|
||||||
mUid = uid;
|
|
||||||
mFolder = folder;
|
|
||||||
mSize = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSize(int size) {
|
|
||||||
mSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void parse(InputStream in) throws IOException, MessagingException {
|
|
||||||
super.parse(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFlag(Flag flag, boolean set) throws MessagingException {
|
|
||||||
super.setFlag(flag, set);
|
|
||||||
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pop3Capabilities {
|
|
||||||
public boolean stls;
|
|
||||||
public boolean top;
|
|
||||||
public boolean user;
|
|
||||||
public boolean uidl;
|
|
||||||
public boolean pipelining;
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return String.format("STLS %b, TOP %b, USER %b, UIDL %b, PIPELINING %b",
|
|
||||||
stls,
|
|
||||||
top,
|
|
||||||
user,
|
|
||||||
uidl,
|
|
||||||
pipelining);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pop3ResponseInputStream extends InputStream {
|
|
||||||
InputStream mIn;
|
|
||||||
boolean mStartOfLine = true;
|
|
||||||
boolean mFinished;
|
|
||||||
|
|
||||||
public Pop3ResponseInputStream(InputStream in) {
|
|
||||||
mIn = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (mFinished) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int d = mIn.read();
|
|
||||||
if (mStartOfLine && d == '.') {
|
|
||||||
d = mIn.read();
|
|
||||||
if (d == '\r') {
|
|
||||||
mFinished = true;
|
|
||||||
mIn.read();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mStartOfLine = (d == '\n');
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.store;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.net.http.DomainNameChecker;
|
|
||||||
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
|
|
||||||
public final class TrustManagerFactory {
|
|
||||||
private static final String LOG_TAG = "TrustManagerFactory";
|
|
||||||
|
|
||||||
private static X509TrustManager sSecureTrustManager;
|
|
||||||
private static X509TrustManager sUnsecureTrustManager;
|
|
||||||
|
|
||||||
private static class SimpleX509TrustManager implements X509TrustManager {
|
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
|
||||||
throws CertificateException {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
|
||||||
throws CertificateException {
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SecureX509TrustManager implements X509TrustManager {
|
|
||||||
private X509TrustManager mTrustManager;
|
|
||||||
private String mHost;
|
|
||||||
|
|
||||||
SecureX509TrustManager(X509TrustManager trustManager, String host) {
|
|
||||||
mTrustManager = trustManager;
|
|
||||||
mHost = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
|
||||||
throws CertificateException {
|
|
||||||
mTrustManager.checkClientTrusted(chain, authType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
|
||||||
throws CertificateException {
|
|
||||||
|
|
||||||
mTrustManager.checkServerTrusted(chain, authType);
|
|
||||||
|
|
||||||
if (!DomainNameChecker.match(chain[0], mHost)) {
|
|
||||||
throw new CertificateException("Certificate domain name does not match "
|
|
||||||
+ mHost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return mTrustManager.getAcceptedIssuers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509");
|
|
||||||
tmf.init((KeyStore) null);
|
|
||||||
TrustManager[] tms = tmf.getTrustManagers();
|
|
||||||
if (tms != null) {
|
|
||||||
for (TrustManager tm : tms) {
|
|
||||||
if (tm instanceof X509TrustManager) {
|
|
||||||
sSecureTrustManager = (X509TrustManager) tm;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.e(LOG_TAG, "Unable to get X509 Trust Manager ", e);
|
|
||||||
} catch (KeyStoreException e) {
|
|
||||||
Log.e(LOG_TAG, "Key Store exception while initializing TrustManagerFactory ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
sUnsecureTrustManager = new SimpleX509TrustManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TrustManagerFactory() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static X509TrustManager get(String host, boolean secure) {
|
|
||||||
return secure ? new SecureX509TrustManager(sSecureTrustManager, host) :
|
|
||||||
sUnsecureTrustManager;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package com.fsck.k9.mail.transport;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple OutputStream that does nothing but count how many bytes are written to it and
|
|
||||||
* makes that count available to callers.
|
|
||||||
*/
|
|
||||||
public class CountingOutputStream extends OutputStream {
|
|
||||||
private long mCount;
|
|
||||||
|
|
||||||
public CountingOutputStream() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCount() {
|
|
||||||
return mCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int oneByte) throws IOException {
|
|
||||||
mCount++;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package com.fsck.k9.mail.transport;
|
|
||||||
|
|
||||||
import java.io.FilterOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public class EOLConvertingOutputStream extends FilterOutputStream {
|
|
||||||
int lastChar;
|
|
||||||
|
|
||||||
public EOLConvertingOutputStream(OutputStream out) {
|
|
||||||
super(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int oneByte) throws IOException {
|
|
||||||
if (oneByte == '\n') {
|
|
||||||
if (lastChar != '\r') {
|
|
||||||
super.write('\r');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.write(oneByte);
|
|
||||||
lastChar = oneByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() throws IOException {
|
|
||||||
if (lastChar == '\r') {
|
|
||||||
super.write('\n');
|
|
||||||
lastChar = '\n';
|
|
||||||
}
|
|
||||||
super.flush();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,376 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.mail.transport;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.SSLException;
|
|
||||||
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.PeekableInputStream;
|
|
||||||
import com.fsck.k9.codec.binary.Base64;
|
|
||||||
import com.fsck.k9.mail.Address;
|
|
||||||
import com.fsck.k9.mail.AuthenticationFailedException;
|
|
||||||
import com.fsck.k9.mail.Message;
|
|
||||||
import com.fsck.k9.mail.MessagingException;
|
|
||||||
import com.fsck.k9.mail.Transport;
|
|
||||||
import com.fsck.k9.mail.CertificateValidationException;
|
|
||||||
import com.fsck.k9.mail.Message.RecipientType;
|
|
||||||
import com.fsck.k9.mail.store.TrustManagerFactory;
|
|
||||||
|
|
||||||
public class SmtpTransport extends Transport {
|
|
||||||
public static final int CONNECTION_SECURITY_NONE = 0;
|
|
||||||
|
|
||||||
public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
|
|
||||||
|
|
||||||
public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
|
|
||||||
|
|
||||||
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
|
|
||||||
|
|
||||||
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
|
|
||||||
|
|
||||||
String mHost;
|
|
||||||
|
|
||||||
int mPort;
|
|
||||||
|
|
||||||
String mUsername;
|
|
||||||
|
|
||||||
String mPassword;
|
|
||||||
|
|
||||||
int mConnectionSecurity;
|
|
||||||
|
|
||||||
boolean mSecure;
|
|
||||||
|
|
||||||
Socket mSocket;
|
|
||||||
|
|
||||||
PeekableInputStream mIn;
|
|
||||||
|
|
||||||
OutputStream mOut;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* smtp://user:password@server:port CONNECTION_SECURITY_NONE
|
|
||||||
* smtp+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
|
|
||||||
* smtp+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
|
|
||||||
* smtp+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
|
|
||||||
* smtp+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
|
|
||||||
*
|
|
||||||
* @param _uri
|
|
||||||
*/
|
|
||||||
public SmtpTransport(String _uri) throws MessagingException {
|
|
||||||
URI uri;
|
|
||||||
try {
|
|
||||||
uri = new URI(_uri);
|
|
||||||
} catch (URISyntaxException use) {
|
|
||||||
throw new MessagingException("Invalid SmtpTransport URI", use);
|
|
||||||
}
|
|
||||||
|
|
||||||
String scheme = uri.getScheme();
|
|
||||||
if (scheme.equals("smtp")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_NONE;
|
|
||||||
mPort = 25;
|
|
||||||
} else if (scheme.equals("smtp+tls")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL;
|
|
||||||
mPort = 25;
|
|
||||||
} else if (scheme.equals("smtp+tls+")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED;
|
|
||||||
mPort = 25;
|
|
||||||
} else if (scheme.equals("smtp+ssl+")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
|
|
||||||
mPort = 465;
|
|
||||||
} else if (scheme.equals("smtp+ssl")) {
|
|
||||||
mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL;
|
|
||||||
mPort = 465;
|
|
||||||
} else {
|
|
||||||
throw new MessagingException("Unsupported protocol");
|
|
||||||
}
|
|
||||||
|
|
||||||
mHost = uri.getHost();
|
|
||||||
|
|
||||||
if (uri.getPort() != -1) {
|
|
||||||
mPort = uri.getPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.getUserInfo() != null) {
|
|
||||||
String[] userInfoParts = uri.getUserInfo().split(":", 2);
|
|
||||||
mUsername = userInfoParts[0];
|
|
||||||
if (userInfoParts.length > 1) {
|
|
||||||
mPassword = userInfoParts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void open() throws MessagingException {
|
|
||||||
try {
|
|
||||||
SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
|
|
||||||
if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
|
|
||||||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
||||||
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
|
|
||||||
sslContext.init(null, new TrustManager[] {
|
|
||||||
TrustManagerFactory.get(mHost, secure)
|
|
||||||
}, new SecureRandom());
|
|
||||||
mSocket = sslContext.getSocketFactory().createSocket();
|
|
||||||
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
|
|
||||||
mSecure = true;
|
|
||||||
} else {
|
|
||||||
mSocket = new Socket();
|
|
||||||
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024));
|
|
||||||
mOut = mSocket.getOutputStream();
|
|
||||||
|
|
||||||
// Eat the banner
|
|
||||||
executeSimpleCommand(null);
|
|
||||||
|
|
||||||
String localHost = "localhost.localdomain";
|
|
||||||
try {
|
|
||||||
InetAddress localAddress = InetAddress.getLocalHost();
|
|
||||||
if (! localAddress.isLoopbackAddress()) {
|
|
||||||
// The loopback address will resolve to 'localhost'
|
|
||||||
// some mail servers only accept qualified hostnames, so make sure
|
|
||||||
// never to override "localhost.localdomain" with "localhost"
|
|
||||||
// TODO - this is a hack. but a better hack than what was there before
|
|
||||||
localHost = localAddress.getHostName();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (Config.LOGD) {
|
|
||||||
if (k9.DEBUG) {
|
|
||||||
Log.d(k9.LOG_TAG, "Unable to look up localhost");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String result = executeSimpleCommand("EHLO " + localHost);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO may need to add code to fall back to HELO I switched it from
|
|
||||||
* using HELO on non STARTTLS connections because of AOL's mail
|
|
||||||
* server. It won't let you use AUTH without EHLO.
|
|
||||||
* We should really be paying more attention to the capabilities
|
|
||||||
* and only attempting auth if it's available, and warning the user
|
|
||||||
* if not.
|
|
||||||
*/
|
|
||||||
if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL
|
|
||||||
|| mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
|
|
||||||
if (result.contains("-STARTTLS")) {
|
|
||||||
executeSimpleCommand("STARTTLS");
|
|
||||||
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
||||||
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
|
|
||||||
sslContext.init(null, new TrustManager[] {
|
|
||||||
TrustManagerFactory.get(mHost, secure)
|
|
||||||
}, new SecureRandom());
|
|
||||||
mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort,
|
|
||||||
true);
|
|
||||||
mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(),
|
|
||||||
1024));
|
|
||||||
mOut = mSocket.getOutputStream();
|
|
||||||
mSecure = true;
|
|
||||||
/*
|
|
||||||
* Now resend the EHLO. Required by RFC2487 Sec. 5.2, and more specifically,
|
|
||||||
* Exim.
|
|
||||||
*/
|
|
||||||
result = executeSimpleCommand("EHLO " + localHost);
|
|
||||||
} else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
|
|
||||||
throw new MessagingException("TLS not supported but required");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* result contains the results of the EHLO in concatenated form
|
|
||||||
*/
|
|
||||||
boolean authLoginSupported = result.matches(".*AUTH.*LOGIN.*$");
|
|
||||||
boolean authPlainSupported = result.matches(".*AUTH.*PLAIN.*$");
|
|
||||||
|
|
||||||
if (mUsername != null && mUsername.length() > 0 && mPassword != null
|
|
||||||
&& mPassword.length() > 0) {
|
|
||||||
if (authPlainSupported) {
|
|
||||||
saslAuthPlain(mUsername, mPassword);
|
|
||||||
}
|
|
||||||
else if (authLoginSupported) {
|
|
||||||
saslAuthLogin(mUsername, mPassword);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new MessagingException("No valid authentication mechanism found.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SSLException e) {
|
|
||||||
throw new CertificateValidationException(e.getMessage(), e);
|
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
throw new MessagingException(
|
|
||||||
"Unable to open connection to SMTP server due to security error.", gse);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new MessagingException("Unable to open connection to SMTP server.", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(Message message) throws MessagingException {
|
|
||||||
close();
|
|
||||||
open();
|
|
||||||
Address[] from = message.getFrom();
|
|
||||||
|
|
||||||
try {
|
|
||||||
executeSimpleCommand("MAIL FROM: " + "<" + from[0].getAddress() + ">");
|
|
||||||
for (Address address : message.getRecipients(RecipientType.TO)) {
|
|
||||||
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
|
|
||||||
}
|
|
||||||
for (Address address : message.getRecipients(RecipientType.CC)) {
|
|
||||||
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
|
|
||||||
}
|
|
||||||
for (Address address : message.getRecipients(RecipientType.BCC)) {
|
|
||||||
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
|
|
||||||
}
|
|
||||||
message.setRecipients(RecipientType.BCC, null);
|
|
||||||
executeSimpleCommand("DATA");
|
|
||||||
// TODO byte stuffing
|
|
||||||
message.writeTo(
|
|
||||||
new EOLConvertingOutputStream(
|
|
||||||
new BufferedOutputStream(mOut, 1024)));
|
|
||||||
executeSimpleCommand("\r\n.");
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new MessagingException("Unable to send message", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
try {
|
|
||||||
mIn.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
mOut.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
mSocket.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
mIn = null;
|
|
||||||
mOut = null;
|
|
||||||
mSocket = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readLine() throws IOException {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
int d;
|
|
||||||
while ((d = mIn.read()) != -1) {
|
|
||||||
if (((char)d) == '\r') {
|
|
||||||
continue;
|
|
||||||
} else if (((char)d) == '\n') {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
sb.append((char)d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String ret = sb.toString();
|
|
||||||
if (Config.LOGD) {
|
|
||||||
if (k9.DEBUG) {
|
|
||||||
Log.d(k9.LOG_TAG, "<<< " + ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeLine(String s) throws IOException {
|
|
||||||
if (Config.LOGD) {
|
|
||||||
if (k9.DEBUG) {
|
|
||||||
Log.d(k9.LOG_TAG, ">>> " + s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mOut.write(s.getBytes());
|
|
||||||
mOut.write('\r');
|
|
||||||
mOut.write('\n');
|
|
||||||
mOut.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String executeSimpleCommand(String command) throws IOException, MessagingException {
|
|
||||||
if (command != null) {
|
|
||||||
writeLine(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
String line = readLine();
|
|
||||||
|
|
||||||
String result = line;
|
|
||||||
|
|
||||||
while (line.length() >= 4 && line.charAt(3) == '-') {
|
|
||||||
line = readLine();
|
|
||||||
result += line.substring(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
char c = result.charAt(0);
|
|
||||||
if ((c == '4') || (c == '5')) {
|
|
||||||
throw new MessagingException(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// C: AUTH LOGIN
|
|
||||||
// S: 334 VXNlcm5hbWU6
|
|
||||||
// C: d2VsZG9u
|
|
||||||
// S: 334 UGFzc3dvcmQ6
|
|
||||||
// C: dzNsZDBu
|
|
||||||
// S: 235 2.0.0 OK Authenticated
|
|
||||||
//
|
|
||||||
// Lines 2-5 of the conversation contain base64-encoded information. The same conversation, with base64 strings decoded, reads:
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// C: AUTH LOGIN
|
|
||||||
// S: 334 Username:
|
|
||||||
// C: weldon
|
|
||||||
// S: 334 Password:
|
|
||||||
// C: w3ld0n
|
|
||||||
// S: 235 2.0.0 OK Authenticated
|
|
||||||
|
|
||||||
private void saslAuthLogin(String username, String password) throws MessagingException,
|
|
||||||
AuthenticationFailedException, IOException {
|
|
||||||
try {
|
|
||||||
executeSimpleCommand("AUTH LOGIN");
|
|
||||||
executeSimpleCommand(new String(Base64.encodeBase64(username.getBytes())));
|
|
||||||
executeSimpleCommand(new String(Base64.encodeBase64(password.getBytes())));
|
|
||||||
}
|
|
||||||
catch (MessagingException me) {
|
|
||||||
if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3') {
|
|
||||||
throw new AuthenticationFailedException("AUTH LOGIN failed (" + me.getMessage()
|
|
||||||
+ ")");
|
|
||||||
}
|
|
||||||
throw me;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saslAuthPlain(String username, String password) throws MessagingException,
|
|
||||||
AuthenticationFailedException, IOException {
|
|
||||||
byte[] data = ("\000" + username + "\000" + password).getBytes();
|
|
||||||
data = new Base64().encode(data);
|
|
||||||
try {
|
|
||||||
executeSimpleCommand("AUTH PLAIN " + new String(data));
|
|
||||||
}
|
|
||||||
catch (MessagingException me) {
|
|
||||||
if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3') {
|
|
||||||
throw new AuthenticationFailedException("AUTH PLAIN failed (" + me.getMessage()
|
|
||||||
+ ")");
|
|
||||||
}
|
|
||||||
throw me;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package com.fsck.k9.mail.transport;
|
|
||||||
|
|
||||||
import java.io.FilterOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class StatusOutputStream extends FilterOutputStream {
|
|
||||||
private long mCount = 0;
|
|
||||||
|
|
||||||
public StatusOutputStream(OutputStream out) {
|
|
||||||
super(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int oneByte) throws IOException {
|
|
||||||
super.write(oneByte);
|
|
||||||
mCount++;
|
|
||||||
if (Config.LOGV) {
|
|
||||||
if (mCount % 1024 == 0) {
|
|
||||||
Log.v(k9.LOG_TAG, "# " + mCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,272 +0,0 @@
|
|||||||
package com.fsck.k9.provider;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.content.ContentProvider;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.MatrixCursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.provider.OpenableColumns;
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.Utility;
|
|
||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A simple ContentProvider that allows file access to Email's attachments.
|
|
||||||
*/
|
|
||||||
public class AttachmentProvider extends ContentProvider {
|
|
||||||
public static final Uri CONTENT_URI = Uri.parse( "content://com.fsck.k9.attachmentprovider");
|
|
||||||
|
|
||||||
private static final String FORMAT_RAW = "RAW";
|
|
||||||
private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
|
|
||||||
|
|
||||||
public static class AttachmentProviderColumns {
|
|
||||||
public static final String _ID = "_id";
|
|
||||||
public static final String DATA = "_data";
|
|
||||||
public static final String DISPLAY_NAME = "_display_name";
|
|
||||||
public static final String SIZE = "_size";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getAttachmentUri(Account account, long id) {
|
|
||||||
return CONTENT_URI.buildUpon()
|
|
||||||
.appendPath(account.getUuid() + ".db")
|
|
||||||
.appendPath(Long.toString(id))
|
|
||||||
.appendPath(FORMAT_RAW)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getAttachmentThumbnailUri(Account account, long id, int width, int height) {
|
|
||||||
return CONTENT_URI.buildUpon()
|
|
||||||
.appendPath(account.getUuid() + ".db")
|
|
||||||
.appendPath(Long.toString(id))
|
|
||||||
.appendPath(FORMAT_THUMBNAIL)
|
|
||||||
.appendPath(Integer.toString(width))
|
|
||||||
.appendPath(Integer.toString(height))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getAttachmentUri(String db, long id) {
|
|
||||||
return CONTENT_URI.buildUpon()
|
|
||||||
.appendPath(db)
|
|
||||||
.appendPath(Long.toString(id))
|
|
||||||
.appendPath(FORMAT_RAW)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreate() {
|
|
||||||
/*
|
|
||||||
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
|
|
||||||
* on startup we'll clean up any .tmp files from the last run.
|
|
||||||
*/
|
|
||||||
File[] files = getContext().getCacheDir().listFiles();
|
|
||||||
for (File file : files) {
|
|
||||||
if (file.getName().endsWith(".tmp")) {
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getType(Uri uri) {
|
|
||||||
List<String> segments = uri.getPathSegments();
|
|
||||||
String dbName = segments.get(0);
|
|
||||||
String id = segments.get(1);
|
|
||||||
String format = segments.get(2);
|
|
||||||
if (FORMAT_THUMBNAIL.equals(format)) {
|
|
||||||
return "image/png";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
|
|
||||||
SQLiteDatabase db = null;
|
|
||||||
Cursor cursor = null;
|
|
||||||
try {
|
|
||||||
db = SQLiteDatabase.openDatabase(path, null, 0);
|
|
||||||
cursor = db.query(
|
|
||||||
"attachments",
|
|
||||||
new String[] { "mime_type" },
|
|
||||||
"id = ?",
|
|
||||||
new String[] { id },
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
cursor.moveToFirst();
|
|
||||||
String type = cursor.getString(0);
|
|
||||||
cursor.close();
|
|
||||||
db.close();
|
|
||||||
return type;
|
|
||||||
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
if (db != null) {
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
|
||||||
List<String> segments = uri.getPathSegments();
|
|
||||||
String dbName = segments.get(0);
|
|
||||||
String id = segments.get(1);
|
|
||||||
String format = segments.get(2);
|
|
||||||
if (FORMAT_THUMBNAIL.equals(format)) {
|
|
||||||
int width = Integer.parseInt(segments.get(3));
|
|
||||||
int height = Integer.parseInt(segments.get(4));
|
|
||||||
String filename = "thmb_" + dbName + "_" + id;
|
|
||||||
File dir = getContext().getCacheDir();
|
|
||||||
File file = new File(dir, filename);
|
|
||||||
if (!file.exists()) {
|
|
||||||
Uri attachmentUri = getAttachmentUri(dbName, Long.parseLong(id));
|
|
||||||
String type = getType(attachmentUri);
|
|
||||||
try {
|
|
||||||
FileInputStream in = new FileInputStream(
|
|
||||||
new File(getContext().getDatabasePath(dbName + "_att"), id));
|
|
||||||
Bitmap thumbnail = createThumbnail(type, in);
|
|
||||||
thumbnail = thumbnail.createScaledBitmap(thumbnail, width, height, true);
|
|
||||||
FileOutputStream out = new FileOutputStream(file);
|
|
||||||
thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
|
|
||||||
out.close();
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return ParcelFileDescriptor.open(
|
|
||||||
new File(getContext().getDatabasePath(dbName + "_att"), id),
|
|
||||||
ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int delete(Uri uri, String arg1, String[] arg2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri insert(Uri uri, ContentValues values) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
|
||||||
String sortOrder) {
|
|
||||||
if (projection == null) {
|
|
||||||
projection =
|
|
||||||
new String[] {
|
|
||||||
AttachmentProviderColumns._ID,
|
|
||||||
AttachmentProviderColumns.DATA,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> segments = uri.getPathSegments();
|
|
||||||
String dbName = segments.get(0);
|
|
||||||
String id = segments.get(1);
|
|
||||||
String format = segments.get(2);
|
|
||||||
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
|
|
||||||
String name = null;
|
|
||||||
int size = -1;
|
|
||||||
SQLiteDatabase db = null;
|
|
||||||
Cursor cursor = null;
|
|
||||||
try {
|
|
||||||
db = SQLiteDatabase.openDatabase(path, null, 0);
|
|
||||||
cursor = db.query(
|
|
||||||
"attachments",
|
|
||||||
new String[] { "name", "size" },
|
|
||||||
"id = ?",
|
|
||||||
new String[] { id },
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
if (!cursor.moveToFirst()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
name = cursor.getString(0);
|
|
||||||
size = cursor.getInt(1);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
if (db != null) {
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MatrixCursor ret = new MatrixCursor(projection);
|
|
||||||
Object[] values = new Object[projection.length];
|
|
||||||
for (int i = 0, count = projection.length; i < count; i++) {
|
|
||||||
String column = projection[i];
|
|
||||||
if (AttachmentProviderColumns._ID.equals(column)) {
|
|
||||||
values[i] = id;
|
|
||||||
}
|
|
||||||
else if (AttachmentProviderColumns.DATA.equals(column)) {
|
|
||||||
values[i] = uri.toString();
|
|
||||||
}
|
|
||||||
else if (AttachmentProviderColumns.DISPLAY_NAME.equals(column)) {
|
|
||||||
values[i] = name;
|
|
||||||
}
|
|
||||||
else if (AttachmentProviderColumns.SIZE.equals(column)) {
|
|
||||||
values[i] = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret.addRow(values);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap createThumbnail(String type, InputStream data) {
|
|
||||||
if(MimeUtility.mimeTypeMatches(type, "image/*")) {
|
|
||||||
return createImageThumbnail(data);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap createImageThumbnail(InputStream data) {
|
|
||||||
try {
|
|
||||||
Bitmap bitmap = BitmapFactory.decodeStream(data);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
catch (OutOfMemoryError oome) {
|
|
||||||
/*
|
|
||||||
* Improperly downloaded images, corrupt bitmaps and the like can commonly
|
|
||||||
* cause OOME due to invalid allocation sizes. We're happy with a null bitmap in
|
|
||||||
* that case. If the system is really out of memory we'll know about it soon
|
|
||||||
* enough.
|
|
||||||
*/
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.service;
|
|
||||||
|
|
||||||
import com.fsck.k9.MessagingController;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
public class BootReceiver extends BroadcastReceiver {
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
|
||||||
MailService.actionReschedule(context);
|
|
||||||
}
|
|
||||||
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
|
|
||||||
MailService.actionCancel(context);
|
|
||||||
}
|
|
||||||
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
|
|
||||||
MailService.actionReschedule(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,193 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9.service;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import android.app.AlarmManager;
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Config;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
|
||||||
import com.fsck.k9.k9;
|
|
||||||
import com.fsck.k9.MessagingController;
|
|
||||||
import com.fsck.k9.MessagingListener;
|
|
||||||
import com.fsck.k9.Preferences;
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.activity.Accounts;
|
|
||||||
import com.fsck.k9.activity.FolderMessageList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public class MailService extends Service {
|
|
||||||
private static final String ACTION_CHECK_MAIL = "com.fsck.k9.intent.action.MAIL_SERVICE_WAKEUP";
|
|
||||||
private static final String ACTION_RESCHEDULE = "com.fsck.k9.intent.action.MAIL_SERVICE_RESCHEDULE";
|
|
||||||
private static final String ACTION_CANCEL = "com.fsck.k9.intent.action.MAIL_SERVICE_CANCEL";
|
|
||||||
|
|
||||||
private Listener mListener = new Listener();
|
|
||||||
|
|
||||||
private int mStartId;
|
|
||||||
|
|
||||||
public static void actionReschedule(Context context) {
|
|
||||||
Intent i = new Intent();
|
|
||||||
i.setClass(context, MailService.class);
|
|
||||||
i.setAction(MailService.ACTION_RESCHEDULE);
|
|
||||||
context.startService(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void actionCancel(Context context) {
|
|
||||||
Intent i = new Intent();
|
|
||||||
i.setClass(context, MailService.class);
|
|
||||||
i.setAction(MailService.ACTION_CANCEL);
|
|
||||||
context.startService(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart(Intent intent, int startId) {
|
|
||||||
super.onStart(intent, startId);
|
|
||||||
this.mStartId = startId;
|
|
||||||
|
|
||||||
MessagingController.getInstance(getApplication()).addListener(mListener);
|
|
||||||
if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
|
|
||||||
if (Config.LOGV) {
|
|
||||||
Log.v(k9.LOG_TAG, "***** MailService *****: checking mail");
|
|
||||||
}
|
|
||||||
MessagingController.getInstance(getApplication()).checkMail(this, null, mListener);
|
|
||||||
}
|
|
||||||
else if (ACTION_CANCEL.equals(intent.getAction())) {
|
|
||||||
if (Config.LOGV) {
|
|
||||||
Log.v(k9.LOG_TAG, "***** MailService *****: cancel");
|
|
||||||
}
|
|
||||||
cancel();
|
|
||||||
stopSelf(startId);
|
|
||||||
}
|
|
||||||
else if (ACTION_RESCHEDULE.equals(intent.getAction())) {
|
|
||||||
if (Config.LOGV) {
|
|
||||||
Log.v(k9.LOG_TAG, "***** MailService *****: reschedule");
|
|
||||||
}
|
|
||||||
reschedule();
|
|
||||||
stopSelf(startId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
MessagingController.getInstance(getApplication()).removeListener(mListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancel() {
|
|
||||||
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
|
|
||||||
Intent i = new Intent();
|
|
||||||
i.setClassName("com.fsck.k9", "com.fsck.k9.service.MailService");
|
|
||||||
i.setAction(ACTION_CHECK_MAIL);
|
|
||||||
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
|
|
||||||
alarmMgr.cancel(pi);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reschedule() {
|
|
||||||
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
|
|
||||||
Intent i = new Intent();
|
|
||||||
i.setClassName("com.fsck.k9", "com.fsck.k9.service.MailService");
|
|
||||||
i.setAction(ACTION_CHECK_MAIL);
|
|
||||||
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
|
|
||||||
|
|
||||||
int shortestInterval = -1;
|
|
||||||
for (Account account : Preferences.getPreferences(this).getAccounts()) {
|
|
||||||
if (account.getAutomaticCheckIntervalMinutes() != -1
|
|
||||||
&& (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) {
|
|
||||||
shortestInterval = account.getAutomaticCheckIntervalMinutes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shortestInterval == -1) {
|
|
||||||
alarmMgr.cancel(pi);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
|
|
||||||
+ (shortestInterval * (60 * 1000)), pi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Listener extends MessagingListener {
|
|
||||||
HashMap<Account, Integer> accountsWithNewMail = new HashMap<Account, Integer>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkMailStarted(Context context, Account account) {
|
|
||||||
accountsWithNewMail.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkMailFailed(Context context, Account account, String reason) {
|
|
||||||
reschedule();
|
|
||||||
stopSelf(mStartId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void synchronizeMailboxFinished(
|
|
||||||
Account account,
|
|
||||||
String folder,
|
|
||||||
int totalMessagesInMailbox,
|
|
||||||
int numNewMessages) {
|
|
||||||
if (account.isNotifyNewMail() && numNewMessages > 0) {
|
|
||||||
accountsWithNewMail.put(account, numNewMessages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkMailFinished(Context context, Account account) {
|
|
||||||
NotificationManager notifMgr = (NotificationManager)context
|
|
||||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
|
|
||||||
if (accountsWithNewMail.size() > 0) {
|
|
||||||
Notification notif = new Notification(R.drawable.stat_notify_email_generic,
|
|
||||||
getString(R.string.notification_new_title), System.currentTimeMillis());
|
|
||||||
boolean vibrate = false;
|
|
||||||
String ringtone = null;
|
|
||||||
if (accountsWithNewMail.size() > 1) {
|
|
||||||
for (Account account1 : accountsWithNewMail.keySet()) {
|
|
||||||
if (account1.isVibrate()) vibrate = true;
|
|
||||||
ringtone = account1.getRingtone();
|
|
||||||
}
|
|
||||||
Intent i = new Intent(context, Accounts.class);
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
|
|
||||||
notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
|
|
||||||
getString(R.string.notification_new_multi_account_fmt,
|
|
||||||
accountsWithNewMail.size()), pi);
|
|
||||||
} else {
|
|
||||||
Account account1 = accountsWithNewMail.keySet().iterator().next();
|
|
||||||
int totalNewMails = accountsWithNewMail.get(account1);
|
|
||||||
Intent i = FolderMessageList.actionHandleAccountIntent(context, account1, k9.INBOX);
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
|
|
||||||
notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
|
|
||||||
getString(R.string.notification_new_one_account_fmt, totalNewMails,
|
|
||||||
account1.getDescription()), pi);
|
|
||||||
vibrate = account1.isVibrate();
|
|
||||||
ringtone = account1.getRingtone();
|
|
||||||
}
|
|
||||||
notif.defaults = Notification.DEFAULT_LIGHTS;
|
|
||||||
notif.sound = TextUtils.isEmpty(ringtone) ? null : Uri.parse(ringtone);
|
|
||||||
if (vibrate) {
|
|
||||||
notif.defaults |= Notification.DEFAULT_VIBRATE;
|
|
||||||
}
|
|
||||||
notifMgr.notify(1, notif);
|
|
||||||
}
|
|
||||||
|
|
||||||
reschedule();
|
|
||||||
stopSelf(mStartId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,332 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.io.Writer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides static utility methods for buffered
|
|
||||||
* copying between sources (<code>InputStream</code>, <code>Reader</code>,
|
|
||||||
* <code>String</code> and <code>byte[]</code>) and destinations
|
|
||||||
* (<code>OutputStream</code>, <code>Writer</code>, <code>String</code> and
|
|
||||||
* <code>byte[]</code>).
|
|
||||||
* <p>
|
|
||||||
* Unless otherwise noted, these <code>copy</code> methods do <em>not</em>
|
|
||||||
* flush or close the streams. Often doing so would require making non-portable
|
|
||||||
* assumptions about the streams' origin and further use. This means that both
|
|
||||||
* streams' <code>close()</code> methods must be called after copying. if one
|
|
||||||
* omits this step, then the stream resources (sockets, file descriptors) are
|
|
||||||
* released when the associated Stream is garbage-collected. It is not a good
|
|
||||||
* idea to rely on this mechanism. For a good overview of the distinction
|
|
||||||
* between "memory management" and "resource management", see
|
|
||||||
* <a href="http://www.unixreview.com/articles/1998/9804/9804ja/ja.htm">this
|
|
||||||
* UnixReview article</a>.
|
|
||||||
* <p>
|
|
||||||
* For byte-to-char methods, a <code>copy</code> variant allows the encoding
|
|
||||||
* to be selected (otherwise the platform default is used). We would like to
|
|
||||||
* encourage you to always specify the encoding because relying on the platform
|
|
||||||
* default can lead to unexpected results.
|
|
||||||
* <p
|
|
||||||
* We don't provide special variants for the <code>copy</code> methods that
|
|
||||||
* let you specify the buffer size because in modern VMs the impact on speed
|
|
||||||
* seems to be minimal. We're using a default buffer size of 4 KB.
|
|
||||||
* <p>
|
|
||||||
* The <code>copy</code> methods use an internal buffer when copying. It is
|
|
||||||
* therefore advisable <em>not</em> to deliberately wrap the stream arguments
|
|
||||||
* to the <code>copy</code> methods in <code>Buffered*</code> streams. For
|
|
||||||
* example, don't do the following:
|
|
||||||
* <pre>
|
|
||||||
* copy( new BufferedInputStream( in ), new BufferedOutputStream( out ) );
|
|
||||||
* </pre>
|
|
||||||
* The rationale is as follows:
|
|
||||||
* <p>
|
|
||||||
* Imagine that an InputStream's read() is a very expensive operation, which
|
|
||||||
* would usually suggest wrapping in a BufferedInputStream. The
|
|
||||||
* BufferedInputStream works by issuing infrequent
|
|
||||||
* {@link java.io.InputStream#read(byte[] b, int off, int len)} requests on the
|
|
||||||
* underlying InputStream, to fill an internal buffer, from which further
|
|
||||||
* <code>read</code> requests can inexpensively get their data (until the buffer
|
|
||||||
* runs out).
|
|
||||||
* <p>
|
|
||||||
* However, the <code>copy</code> methods do the same thing, keeping an
|
|
||||||
* internal buffer, populated by
|
|
||||||
* {@link InputStream#read(byte[] b, int off, int len)} requests. Having two
|
|
||||||
* buffers (or three if the destination stream is also buffered) is pointless,
|
|
||||||
* and the unnecessary buffer management hurts performance slightly (about 3%,
|
|
||||||
* according to some simple experiments).
|
|
||||||
* <p>
|
|
||||||
* Behold, intrepid explorers; a map of this class:
|
|
||||||
* <pre>
|
|
||||||
* Method Input Output Dependency
|
|
||||||
* ------ ----- ------ -------
|
|
||||||
* 1 copy InputStream OutputStream (primitive)
|
|
||||||
* 2 copy Reader Writer (primitive)
|
|
||||||
*
|
|
||||||
* 3 copy InputStream Writer 2
|
|
||||||
*
|
|
||||||
* 4 copy Reader OutputStream 2
|
|
||||||
*
|
|
||||||
* 5 copy String OutputStream 2
|
|
||||||
* 6 copy String Writer (trivial)
|
|
||||||
*
|
|
||||||
* 7 copy byte[] Writer 3
|
|
||||||
* 8 copy byte[] OutputStream (trivial)
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Note that only the first two methods shuffle bytes; the rest use these
|
|
||||||
* two, or (if possible) copy using native Java copy methods. As there are
|
|
||||||
* method variants to specify the encoding, each row may
|
|
||||||
* correspond to up to 2 methods.
|
|
||||||
* <p>
|
|
||||||
* Origin of code: Excalibur.
|
|
||||||
*
|
|
||||||
* @author Peter Donald
|
|
||||||
* @author Jeff Turner
|
|
||||||
* @author Matthew Hawthorne
|
|
||||||
* @version $Id: CopyUtils.java 437680 2006-08-28 11:57:00Z scolebourne $
|
|
||||||
* @deprecated Use IOUtils. Will be removed in 2.0.
|
|
||||||
* Methods renamed to IOUtils.write() or IOUtils.copy().
|
|
||||||
* Null handling behaviour changed in IOUtils (null data does not
|
|
||||||
* throw NullPointerException).
|
|
||||||
*/
|
|
||||||
public class CopyUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default size of the buffer.
|
|
||||||
*/
|
|
||||||
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instances should NOT be constructed in standard programming.
|
|
||||||
*/
|
|
||||||
public CopyUtils() { }
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// byte[] -> OutputStream
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
|
|
||||||
* @param input the byte array to read from
|
|
||||||
* @param output the <code>OutputStream</code> to write to
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(byte[] input, OutputStream output)
|
|
||||||
throws IOException {
|
|
||||||
output.write(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// byte[] -> Writer
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy and convert bytes from a <code>byte[]</code> to chars on a
|
|
||||||
* <code>Writer</code>.
|
|
||||||
* The platform's default encoding is used for the byte-to-char conversion.
|
|
||||||
* @param input the byte array to read from
|
|
||||||
* @param output the <code>Writer</code> to write to
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(byte[] input, Writer output)
|
|
||||||
throws IOException {
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(input);
|
|
||||||
copy(in, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy and convert bytes from a <code>byte[]</code> to chars on a
|
|
||||||
* <code>Writer</code>, using the specified encoding.
|
|
||||||
* @param input the byte array to read from
|
|
||||||
* @param output the <code>Writer</code> to write to
|
|
||||||
* @param encoding The name of a supported character encoding. See the
|
|
||||||
* <a href="http://www.iana.org/assignments/character-sets">IANA
|
|
||||||
* Charset Registry</a> for a list of valid encoding types.
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(
|
|
||||||
byte[] input,
|
|
||||||
Writer output,
|
|
||||||
String encoding)
|
|
||||||
throws IOException {
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(input);
|
|
||||||
copy(in, output, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Core copy methods
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy bytes from an <code>InputStream</code> to an
|
|
||||||
* <code>OutputStream</code>.
|
|
||||||
* @param input the <code>InputStream</code> to read from
|
|
||||||
* @param output the <code>OutputStream</code> to write to
|
|
||||||
* @return the number of bytes copied
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static int copy(
|
|
||||||
InputStream input,
|
|
||||||
OutputStream output)
|
|
||||||
throws IOException {
|
|
||||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
|
||||||
int count = 0;
|
|
||||||
int n = 0;
|
|
||||||
while (-1 != (n = input.read(buffer))) {
|
|
||||||
output.write(buffer, 0, n);
|
|
||||||
count += n;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Reader -> Writer
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy chars from a <code>Reader</code> to a <code>Writer</code>.
|
|
||||||
* @param input the <code>Reader</code> to read from
|
|
||||||
* @param output the <code>Writer</code> to write to
|
|
||||||
* @return the number of characters copied
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static int copy(
|
|
||||||
Reader input,
|
|
||||||
Writer output)
|
|
||||||
throws IOException {
|
|
||||||
char[] buffer = new char[DEFAULT_BUFFER_SIZE];
|
|
||||||
int count = 0;
|
|
||||||
int n = 0;
|
|
||||||
while (-1 != (n = input.read(buffer))) {
|
|
||||||
output.write(buffer, 0, n);
|
|
||||||
count += n;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// InputStream -> Writer
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy and convert bytes from an <code>InputStream</code> to chars on a
|
|
||||||
* <code>Writer</code>.
|
|
||||||
* The platform's default encoding is used for the byte-to-char conversion.
|
|
||||||
* @param input the <code>InputStream</code> to read from
|
|
||||||
* @param output the <code>Writer</code> to write to
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(
|
|
||||||
InputStream input,
|
|
||||||
Writer output)
|
|
||||||
throws IOException {
|
|
||||||
InputStreamReader in = new InputStreamReader(input);
|
|
||||||
copy(in, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy and convert bytes from an <code>InputStream</code> to chars on a
|
|
||||||
* <code>Writer</code>, using the specified encoding.
|
|
||||||
* @param input the <code>InputStream</code> to read from
|
|
||||||
* @param output the <code>Writer</code> to write to
|
|
||||||
* @param encoding The name of a supported character encoding. See the
|
|
||||||
* <a href="http://www.iana.org/assignments/character-sets">IANA
|
|
||||||
* Charset Registry</a> for a list of valid encoding types.
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(
|
|
||||||
InputStream input,
|
|
||||||
Writer output,
|
|
||||||
String encoding)
|
|
||||||
throws IOException {
|
|
||||||
InputStreamReader in = new InputStreamReader(input, encoding);
|
|
||||||
copy(in, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Reader -> OutputStream
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize chars from a <code>Reader</code> to bytes on an
|
|
||||||
* <code>OutputStream</code>, and flush the <code>OutputStream</code>.
|
|
||||||
* @param input the <code>Reader</code> to read from
|
|
||||||
* @param output the <code>OutputStream</code> to write to
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(
|
|
||||||
Reader input,
|
|
||||||
OutputStream output)
|
|
||||||
throws IOException {
|
|
||||||
OutputStreamWriter out = new OutputStreamWriter(output);
|
|
||||||
copy(input, out);
|
|
||||||
// XXX Unless anyone is planning on rewriting OutputStreamWriter, we
|
|
||||||
// have to flush here.
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// String -> OutputStream
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize chars from a <code>String</code> to bytes on an
|
|
||||||
* <code>OutputStream</code>, and
|
|
||||||
* flush the <code>OutputStream</code>.
|
|
||||||
* @param input the <code>String</code> to read from
|
|
||||||
* @param output the <code>OutputStream</code> to write to
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(
|
|
||||||
String input,
|
|
||||||
OutputStream output)
|
|
||||||
throws IOException {
|
|
||||||
StringReader in = new StringReader(input);
|
|
||||||
OutputStreamWriter out = new OutputStreamWriter(output);
|
|
||||||
copy(in, out);
|
|
||||||
// XXX Unless anyone is planning on rewriting OutputStreamWriter, we
|
|
||||||
// have to flush here.
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// String -> Writer
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy chars from a <code>String</code> to a <code>Writer</code>.
|
|
||||||
* @param input the <code>String</code> to read from
|
|
||||||
* @param output the <code>Writer</code> to write to
|
|
||||||
* @throws IOException In case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void copy(String input, Writer output)
|
|
||||||
throws IOException {
|
|
||||||
output.write(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,620 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileFilter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.apache.commons.io.filefilter.FileFilterUtils;
|
|
||||||
import org.apache.commons.io.filefilter.IOFileFilter;
|
|
||||||
import org.apache.commons.io.filefilter.TrueFileFilter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class that walks through a directory hierarchy and provides
|
|
||||||
* subclasses with convenient hooks to add specific behaviour.
|
|
||||||
* <p>
|
|
||||||
* This class operates with a {@link FileFilter} and maximum depth to
|
|
||||||
* limit the files and direcories visited.
|
|
||||||
* Commons IO supplies many common filter implementations in the
|
|
||||||
* <a href="filefilter/package-summary.html"> filefilter</a> package.
|
|
||||||
* <p>
|
|
||||||
* The following sections describe:
|
|
||||||
* <ul>
|
|
||||||
* <li><a href="#example">1. Example Implementation</a> - example
|
|
||||||
* <code>FileCleaner</code> implementation.</li>
|
|
||||||
* <li><a href="#filter">2. Filter Example</a> - using
|
|
||||||
* {@link FileFilter}(s) with <code>DirectoryWalker</code>.</li>
|
|
||||||
* <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation
|
|
||||||
* behaviour.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <a name="example"></a>
|
|
||||||
* <h3>1. Example Implementation</h3>
|
|
||||||
*
|
|
||||||
* There are many possible extensions, for example, to delete all
|
|
||||||
* files and '.svn' directories, and return a list of deleted files:
|
|
||||||
* <pre>
|
|
||||||
* public class FileCleaner extends DirectoryWalker {
|
|
||||||
*
|
|
||||||
* public FileCleaner() {
|
|
||||||
* super();
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public List clean(File startDirectory) {
|
|
||||||
* List results = new ArrayList();
|
|
||||||
* walk(startDirectory, results);
|
|
||||||
* return results;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* protected boolean handleDirectory(File directory, int depth, Collection results) {
|
|
||||||
* // delete svn directories and then skip
|
|
||||||
* if (".svn".equals(directory.getName())) {
|
|
||||||
* directory.delete();
|
|
||||||
* return false;
|
|
||||||
* } else {
|
|
||||||
* return true;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* protected void handleFile(File file, int depth, Collection results) {
|
|
||||||
* // delete file and add to list of deleted
|
|
||||||
* file.delete();
|
|
||||||
* results.add(file);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <a name="filter"></a>
|
|
||||||
* <h3>2. Filter Example</h3>
|
|
||||||
*
|
|
||||||
* Choosing which directories and files to process can be a key aspect
|
|
||||||
* of using this class. This information can be setup in three ways,
|
|
||||||
* via three different constructors.
|
|
||||||
* <p>
|
|
||||||
* The first option is to visit all directories and files.
|
|
||||||
* This is achieved via the no-args constructor.
|
|
||||||
* <p>
|
|
||||||
* The second constructor option is to supply a single {@link FileFilter}
|
|
||||||
* that describes the files and directories to visit. Care must be taken
|
|
||||||
* with this option as the same filter is used for both directories
|
|
||||||
* and files.
|
|
||||||
* <p>
|
|
||||||
* For example, if you wanted all directories which are not hidden
|
|
||||||
* and files which end in ".txt":
|
|
||||||
* <pre>
|
|
||||||
* public class FooDirectoryWalker extends DirectoryWalker {
|
|
||||||
* public FooDirectoryWalker(FileFilter filter) {
|
|
||||||
* super(filter, -1);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // Build up the filters and create the walker
|
|
||||||
* // Create a filter for Non-hidden directories
|
|
||||||
* IOFileFilter fooDirFilter =
|
|
||||||
* FileFilterUtils.andFileFilter(FileFilterUtils.directoryFileFilter,
|
|
||||||
* HiddenFileFilter.VISIBLE);
|
|
||||||
*
|
|
||||||
* // Create a filter for Files ending in ".txt"
|
|
||||||
* IOFileFilter fooFileFilter =
|
|
||||||
* FileFilterUtils.andFileFilter(FileFilterUtils.fileFileFilter,
|
|
||||||
* FileFilterUtils.suffixFileFilter(".txt"));
|
|
||||||
*
|
|
||||||
* // Combine the directory and file filters using an OR condition
|
|
||||||
* java.io.FileFilter fooFilter =
|
|
||||||
* FileFilterUtils.orFileFilter(fooDirFilter, fooFileFilter);
|
|
||||||
*
|
|
||||||
* // Use the filter to construct a DirectoryWalker implementation
|
|
||||||
* FooDirectoryWalker walker = new FooDirectoryWalker(fooFilter);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* The third constructor option is to specify separate filters, one for
|
|
||||||
* directories and one for files. These are combined internally to form
|
|
||||||
* the correct <code>FileFilter</code>, something which is very easy to
|
|
||||||
* get wrong when attempted manually, particularly when trying to
|
|
||||||
* express constructs like 'any file in directories named docs'.
|
|
||||||
* <p>
|
|
||||||
* For example, if you wanted all directories which are not hidden
|
|
||||||
* and files which end in ".txt":
|
|
||||||
* <pre>
|
|
||||||
* public class FooDirectoryWalker extends DirectoryWalker {
|
|
||||||
* public FooDirectoryWalker(IOFileFilter dirFilter, IOFileFilter fileFilter) {
|
|
||||||
* super(dirFilter, fileFilter, -1);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // Use the filters to construct the walker
|
|
||||||
* FooDirectoryWalker walker = new FooDirectoryWalker(
|
|
||||||
* HiddenFileFilter.VISIBLE,
|
|
||||||
* FileFilterUtils.suffixFileFilter(".txt"),
|
|
||||||
* );
|
|
||||||
* </pre>
|
|
||||||
* This is much simpler than the previous example, and is why it is the preferred
|
|
||||||
* option for filtering.
|
|
||||||
*
|
|
||||||
* <a name="cancel"></a>
|
|
||||||
* <h3>3. Cancellation</h3>
|
|
||||||
*
|
|
||||||
* The DirectoryWalker contains some of the logic required for cancel processing.
|
|
||||||
* Subclasses must complete the implementation.
|
|
||||||
* <p>
|
|
||||||
* What <code>DirectoryWalker</code> does provide for cancellation is:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link CancelException} which can be thrown in any of the
|
|
||||||
* <i>lifecycle</i> methods to stop processing.</li>
|
|
||||||
* <li>The <code>walk()</code> method traps thrown {@link CancelException}
|
|
||||||
* and calls the <code>handleCancelled()</code> method, providing
|
|
||||||
* a place for custom cancel processing.</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* Implementations need to provide:
|
|
||||||
* <ul>
|
|
||||||
* <li>The decision logic on whether to cancel processing or not.</li>
|
|
||||||
* <li>Constructing and throwing a {@link CancelException}.</li>
|
|
||||||
* <li>Custom cancel processing in the <code>handleCancelled()</code> method.
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* Two possible scenarios are envisaged for cancellation:
|
|
||||||
* <ul>
|
|
||||||
* <li><a href="#external">3.1 External / Mult-threaded</a> - cancellation being
|
|
||||||
* decided/initiated by an external process.</li>
|
|
||||||
* <li><a href="#internal">3.2 Internal</a> - cancellation being decided/initiated
|
|
||||||
* from within a DirectoryWalker implementation.</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* The following sections provide example implementations for these two different
|
|
||||||
* scenarios.
|
|
||||||
*
|
|
||||||
* <a name="external"></a>
|
|
||||||
* <h4>3.1 External / Multi-threaded</h4>
|
|
||||||
*
|
|
||||||
* This example provides a public <code>cancel()</code> method that can be
|
|
||||||
* called by another thread to stop the processing. A typical example use-case
|
|
||||||
* would be a cancel button on a GUI. Calling this method sets a
|
|
||||||
* <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930">
|
|
||||||
* volatile</a> flag to ensure it will work properly in a multi-threaded environment.
|
|
||||||
* The flag is returned by the <code>handleIsCancelled()</code> method, which
|
|
||||||
* will cause the walk to stop immediately. The <code>handleCancelled()</code>
|
|
||||||
* method will be the next, and last, callback method received once cancellation
|
|
||||||
* has occurred.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* public class FooDirectoryWalker extends DirectoryWalker {
|
|
||||||
*
|
|
||||||
* private volatile boolean cancelled = false;
|
|
||||||
*
|
|
||||||
* public void cancel() {
|
|
||||||
* cancelled = true;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* private void handleIsCancelled(File file, int depth, Collection results) {
|
|
||||||
* return cancelled;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
|
|
||||||
* // implement processing required when a cancellation occurs
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <a name="internal"></a>
|
|
||||||
* <h4>3.2 Internal</h4>
|
|
||||||
*
|
|
||||||
* This shows an example of how internal cancellation processing could be implemented.
|
|
||||||
* <b>Note</b> the decision logic and throwing a {@link CancelException} could be implemented
|
|
||||||
* in any of the <i>lifecycle</i> methods.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* public class BarDirectoryWalker extends DirectoryWalker {
|
|
||||||
*
|
|
||||||
* protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
|
|
||||||
* // cancel if hidden directory
|
|
||||||
* if (directory.isHidden()) {
|
|
||||||
* throw new CancelException(file, depth);
|
|
||||||
* }
|
|
||||||
* return true;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* protected void handleFile(File file, int depth, Collection results) throws IOException {
|
|
||||||
* // cancel if read-only file
|
|
||||||
* if (!file.canWrite()) {
|
|
||||||
* throw new CancelException(file, depth);
|
|
||||||
* }
|
|
||||||
* results.add(file);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
|
|
||||||
* // implement processing required when a cancellation occurs
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @since Commons IO 1.3
|
|
||||||
* @version $Revision: 424748 $
|
|
||||||
*/
|
|
||||||
public abstract class DirectoryWalker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The file filter to use to filter files and directories.
|
|
||||||
*/
|
|
||||||
private final FileFilter filter;
|
|
||||||
/**
|
|
||||||
* The limit on the directory depth to walk.
|
|
||||||
*/
|
|
||||||
private final int depthLimit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an instance with no filtering and unlimited <i>depth</i>.
|
|
||||||
*/
|
|
||||||
protected DirectoryWalker() {
|
|
||||||
this(null, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an instance with a filter and limit the <i>depth</i> navigated to.
|
|
||||||
* <p>
|
|
||||||
* The filter controls which files and directories will be navigated to as
|
|
||||||
* part of the walk. The {@link FileFilterUtils} class is useful for combining
|
|
||||||
* various filters together. A <code>null</code> filter means that no
|
|
||||||
* filtering should occur and all files and directories will be visited.
|
|
||||||
*
|
|
||||||
* @param filter the filter to apply, null means visit all files
|
|
||||||
* @param depthLimit controls how <i>deep</i> the hierarchy is
|
|
||||||
* navigated to (less than 0 means unlimited)
|
|
||||||
*/
|
|
||||||
protected DirectoryWalker(FileFilter filter, int depthLimit) {
|
|
||||||
this.filter = filter;
|
|
||||||
this.depthLimit = depthLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an instance with a directory and a file filter and an optional
|
|
||||||
* limit on the <i>depth</i> navigated to.
|
|
||||||
* <p>
|
|
||||||
* The filters control which files and directories will be navigated to as part
|
|
||||||
* of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)}
|
|
||||||
* and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters.
|
|
||||||
* A <code>null</code> filter means that no filtering should occur.
|
|
||||||
*
|
|
||||||
* @param directoryFilter the filter to apply to directories, null means visit all directories
|
|
||||||
* @param fileFilter the filter to apply to files, null means visit all files
|
|
||||||
* @param depthLimit controls how <i>deep</i> the hierarchy is
|
|
||||||
* navigated to (less than 0 means unlimited)
|
|
||||||
*/
|
|
||||||
protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, int depthLimit) {
|
|
||||||
if (directoryFilter == null && fileFilter == null) {
|
|
||||||
this.filter = null;
|
|
||||||
} else {
|
|
||||||
directoryFilter = (directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE);
|
|
||||||
fileFilter = (fileFilter != null ? fileFilter : TrueFileFilter.TRUE);
|
|
||||||
directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter);
|
|
||||||
fileFilter = FileFilterUtils.makeFileOnly(fileFilter);
|
|
||||||
this.filter = FileFilterUtils.orFileFilter(directoryFilter, fileFilter);
|
|
||||||
}
|
|
||||||
this.depthLimit = depthLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Internal method that walks the directory hierarchy in a depth-first manner.
|
|
||||||
* <p>
|
|
||||||
* Users of this class do not need to call this method. This method will
|
|
||||||
* be called automatically by another (public) method on the specific subclass.
|
|
||||||
* <p>
|
|
||||||
* Writers of subclasses should call this method to start the directory walk.
|
|
||||||
* Once called, this method will emit events as it walks the hierarchy.
|
|
||||||
* The event methods have the prefix <code>handle</code>.
|
|
||||||
*
|
|
||||||
* @param startDirectory the directory to start from, not null
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws NullPointerException if the start directory is null
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected final void walk(File startDirectory, Collection results) throws IOException {
|
|
||||||
if (startDirectory == null) {
|
|
||||||
throw new NullPointerException("Start Directory is null");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
handleStart(startDirectory, results);
|
|
||||||
walk(startDirectory, 0, results);
|
|
||||||
handleEnd(results);
|
|
||||||
} catch(CancelException cancel) {
|
|
||||||
handleCancelled(startDirectory, results, cancel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main recursive method to examine the directory hierarchy.
|
|
||||||
*
|
|
||||||
* @param directory the directory to examine, not null
|
|
||||||
* @param depth the directory level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
private void walk(File directory, int depth, Collection results) throws IOException {
|
|
||||||
checkIfCancelled(directory, depth, results);
|
|
||||||
if (handleDirectory(directory, depth, results)) {
|
|
||||||
handleDirectoryStart(directory, depth, results);
|
|
||||||
int childDepth = depth + 1;
|
|
||||||
if (depthLimit < 0 || childDepth <= depthLimit) {
|
|
||||||
checkIfCancelled(directory, depth, results);
|
|
||||||
File[] childFiles = (filter == null ? directory.listFiles() : directory.listFiles(filter));
|
|
||||||
if (childFiles == null) {
|
|
||||||
handleRestricted(directory, childDepth, results);
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < childFiles.length; i++) {
|
|
||||||
File childFile = childFiles[i];
|
|
||||||
if (childFile.isDirectory()) {
|
|
||||||
walk(childFile, childDepth, results);
|
|
||||||
} else {
|
|
||||||
checkIfCancelled(childFile, childDepth, results);
|
|
||||||
handleFile(childFile, childDepth, results);
|
|
||||||
checkIfCancelled(childFile, childDepth, results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleDirectoryEnd(directory, depth, results);
|
|
||||||
}
|
|
||||||
checkIfCancelled(directory, depth, results);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Checks whether the walk has been cancelled by calling {@link #handleIsCancelled},
|
|
||||||
* throwing a <code>CancelException</code> if it has.
|
|
||||||
* <p>
|
|
||||||
* Writers of subclasses should not normally call this method as it is called
|
|
||||||
* automatically by the walk of the tree. However, sometimes a single method,
|
|
||||||
* typically {@link #handleFile}, may take a long time to run. In that case,
|
|
||||||
* you may wish to check for cancellation by calling this method.
|
|
||||||
*
|
|
||||||
* @param file the current file being processed
|
|
||||||
* @param depth the current file level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected final void checkIfCancelled(File file, int depth, Collection results) throws IOException {
|
|
||||||
if (handleIsCancelled(file, depth, results)) {
|
|
||||||
throw new CancelException(file, depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked to determine if the entire walk
|
|
||||||
* operation should be immediately cancelled.
|
|
||||||
* <p>
|
|
||||||
* This method should be implemented by those subclasses that want to
|
|
||||||
* provide a public <code>cancel()</code> method available from another
|
|
||||||
* thread. The design pattern for the subclass should be as follows:
|
|
||||||
* <pre>
|
|
||||||
* public class FooDirectoryWalker extends DirectoryWalker {
|
|
||||||
* private volatile boolean cancelled = false;
|
|
||||||
*
|
|
||||||
* public void cancel() {
|
|
||||||
* cancelled = true;
|
|
||||||
* }
|
|
||||||
* private void handleIsCancelled(File file, int depth, Collection results) {
|
|
||||||
* return cancelled;
|
|
||||||
* }
|
|
||||||
* protected void handleCancelled(File startDirectory,
|
|
||||||
* Collection results, CancelException cancel) {
|
|
||||||
* // implement processing required when a cancellation occurs
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* If this method returns true, then the directory walk is immediately
|
|
||||||
* cancelled. The next callback method will be {@link #handleCancelled}.
|
|
||||||
* <p>
|
|
||||||
* This implementation returns false.
|
|
||||||
*
|
|
||||||
* @param file the file or directory being processed
|
|
||||||
* @param depth the current directory level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @return true if the walk has been cancelled
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected boolean handleIsCancelled(
|
|
||||||
File file, int depth, Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
return false; // not cancelled
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked when the operation is cancelled.
|
|
||||||
* The file being processed when the cancellation occurred can be
|
|
||||||
* obtained from the exception.
|
|
||||||
* <p>
|
|
||||||
* This implementation just re-throws the {@link CancelException}.
|
|
||||||
*
|
|
||||||
* @param startDirectory the directory that the walk started from
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @param cancel the exception throw to cancel further processing
|
|
||||||
* containing details at the point of cancellation.
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected void handleCancelled(File startDirectory, Collection results,
|
|
||||||
CancelException cancel) throws IOException {
|
|
||||||
// re-throw exception - overridable by subclass
|
|
||||||
throw cancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked at the start of processing.
|
|
||||||
* <p>
|
|
||||||
* This implementation does nothing.
|
|
||||||
*
|
|
||||||
* @param startDirectory the directory to start from
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected void handleStart(File startDirectory, Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked to determine if a directory should be processed.
|
|
||||||
* <p>
|
|
||||||
* This method returns a boolean to indicate if the directory should be examined or not.
|
|
||||||
* If you return false, the entire directory and any subdirectories will be skipped.
|
|
||||||
* Note that this functionality is in addition to the filtering by file filter.
|
|
||||||
* <p>
|
|
||||||
* This implementation does nothing and returns true.
|
|
||||||
*
|
|
||||||
* @param directory the current directory being processed
|
|
||||||
* @param depth the current directory level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @return true to process this directory, false to skip this directory
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
return true; // process directory
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked at the start of processing each directory.
|
|
||||||
* <p>
|
|
||||||
* This implementation does nothing.
|
|
||||||
*
|
|
||||||
* @param directory the current directory being processed
|
|
||||||
* @param depth the current directory level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected void handleDirectoryStart(File directory, int depth, Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked for each (non-directory) file.
|
|
||||||
* <p>
|
|
||||||
* This implementation does nothing.
|
|
||||||
*
|
|
||||||
* @param file the current file being processed
|
|
||||||
* @param depth the current directory level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected void handleFile(File file, int depth, Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked for each restricted directory.
|
|
||||||
* <p>
|
|
||||||
* This implementation does nothing.
|
|
||||||
*
|
|
||||||
* @param directory the restricted directory
|
|
||||||
* @param depth the current directory level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected void handleRestricted(File directory, int depth, Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked at the end of processing each directory.
|
|
||||||
* <p>
|
|
||||||
* This implementation does nothing.
|
|
||||||
*
|
|
||||||
* @param directory the directory being processed
|
|
||||||
* @param depth the current directory level (starting directory = 0)
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected void handleDirectoryEnd(File directory, int depth, Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable callback method invoked at the end of processing.
|
|
||||||
* <p>
|
|
||||||
* This implementation does nothing.
|
|
||||||
*
|
|
||||||
* @param results the collection of result objects, may be updated
|
|
||||||
* @throws IOException if an I/O Error occurs
|
|
||||||
*/
|
|
||||||
protected void handleEnd(Collection results) throws IOException {
|
|
||||||
// do nothing - overridable by subclass
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* CancelException is thrown in DirectoryWalker to cancel the current
|
|
||||||
* processing.
|
|
||||||
*/
|
|
||||||
public static class CancelException extends IOException {
|
|
||||||
|
|
||||||
/** Serialization id. */
|
|
||||||
private static final long serialVersionUID = 1347339620135041008L;
|
|
||||||
|
|
||||||
/** The file being processed when the exception was thrown. */
|
|
||||||
private File file;
|
|
||||||
/** The file depth when the exception was thrown. */
|
|
||||||
private int depth = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a <code>CancelException</code> with
|
|
||||||
* the file and depth when cancellation occurred.
|
|
||||||
*
|
|
||||||
* @param file the file when the operation was cancelled, may be null
|
|
||||||
* @param depth the depth when the operation was cancelled, may be null
|
|
||||||
*/
|
|
||||||
public CancelException(File file, int depth) {
|
|
||||||
this("Operation Cancelled", file, depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a <code>CancelException</code> with
|
|
||||||
* an appropriate message and the file and depth when
|
|
||||||
* cancellation occurred.
|
|
||||||
*
|
|
||||||
* @param message the detail message
|
|
||||||
* @param file the file when the operation was cancelled
|
|
||||||
* @param depth the depth when the operation was cancelled
|
|
||||||
*/
|
|
||||||
public CancelException(String message, File file, int depth) {
|
|
||||||
super(message);
|
|
||||||
this.file = file;
|
|
||||||
this.depth = depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the file when the operation was cancelled.
|
|
||||||
*
|
|
||||||
* @return the file when the operation was cancelled
|
|
||||||
*/
|
|
||||||
public File getFile() {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the depth when the operation was cancelled.
|
|
||||||
*
|
|
||||||
* @return the depth when the operation was cancelled
|
|
||||||
*/
|
|
||||||
public int getDepth() {
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,489 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility code for dealing with different endian systems.
|
|
||||||
* <p>
|
|
||||||
* Different computer architectures adopt different conventions for
|
|
||||||
* byte ordering. In so-called "Little Endian" architectures (eg Intel),
|
|
||||||
* the low-order byte is stored in memory at the lowest address, and
|
|
||||||
* subsequent bytes at higher addresses. For "Big Endian" architectures
|
|
||||||
* (eg Motorola), the situation is reversed.
|
|
||||||
* This class helps you solve this incompatability.
|
|
||||||
* <p>
|
|
||||||
* Origin of code: Excalibur
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:peter@apache.org">Peter Donald</a>
|
|
||||||
* @version $Id: EndianUtils.java 539632 2007-05-18 23:37:59Z bayard $
|
|
||||||
* @see org.apache.commons.io.input.SwappedDataInputStream
|
|
||||||
*/
|
|
||||||
public class EndianUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instances should NOT be constructed in standard programming.
|
|
||||||
*/
|
|
||||||
public EndianUtils() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================== Swapping routines
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a "short" value between endian systems.
|
|
||||||
* @param value value to convert
|
|
||||||
* @return the converted value
|
|
||||||
*/
|
|
||||||
public static short swapShort(short value) {
|
|
||||||
return (short) ( ( ( ( value >> 0 ) & 0xff ) << 8 ) +
|
|
||||||
( ( ( value >> 8 ) & 0xff ) << 0 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a "int" value between endian systems.
|
|
||||||
* @param value value to convert
|
|
||||||
* @return the converted value
|
|
||||||
*/
|
|
||||||
public static int swapInteger(int value) {
|
|
||||||
return
|
|
||||||
( ( ( value >> 0 ) & 0xff ) << 24 ) +
|
|
||||||
( ( ( value >> 8 ) & 0xff ) << 16 ) +
|
|
||||||
( ( ( value >> 16 ) & 0xff ) << 8 ) +
|
|
||||||
( ( ( value >> 24 ) & 0xff ) << 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a "long" value between endian systems.
|
|
||||||
* @param value value to convert
|
|
||||||
* @return the converted value
|
|
||||||
*/
|
|
||||||
public static long swapLong(long value) {
|
|
||||||
return
|
|
||||||
( ( ( value >> 0 ) & 0xff ) << 56 ) +
|
|
||||||
( ( ( value >> 8 ) & 0xff ) << 48 ) +
|
|
||||||
( ( ( value >> 16 ) & 0xff ) << 40 ) +
|
|
||||||
( ( ( value >> 24 ) & 0xff ) << 32 ) +
|
|
||||||
( ( ( value >> 32 ) & 0xff ) << 24 ) +
|
|
||||||
( ( ( value >> 40 ) & 0xff ) << 16 ) +
|
|
||||||
( ( ( value >> 48 ) & 0xff ) << 8 ) +
|
|
||||||
( ( ( value >> 56 ) & 0xff ) << 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a "float" value between endian systems.
|
|
||||||
* @param value value to convert
|
|
||||||
* @return the converted value
|
|
||||||
*/
|
|
||||||
public static float swapFloat(float value) {
|
|
||||||
return Float.intBitsToFloat( swapInteger( Float.floatToIntBits( value ) ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a "double" value between endian systems.
|
|
||||||
* @param value value to convert
|
|
||||||
* @return the converted value
|
|
||||||
*/
|
|
||||||
public static double swapDouble(double value) {
|
|
||||||
return Double.longBitsToDouble( swapLong( Double.doubleToLongBits( value ) ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================== Swapping read/write routines
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "short" value to a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param data target byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @param value value to write
|
|
||||||
*/
|
|
||||||
public static void writeSwappedShort(byte[] data, int offset, short value) {
|
|
||||||
data[ offset + 0 ] = (byte)( ( value >> 0 ) & 0xff );
|
|
||||||
data[ offset + 1 ] = (byte)( ( value >> 8 ) & 0xff );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "short" value from a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param data source byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @return the value read
|
|
||||||
*/
|
|
||||||
public static short readSwappedShort(byte[] data, int offset) {
|
|
||||||
return (short)( ( ( data[ offset + 0 ] & 0xff ) << 0 ) +
|
|
||||||
( ( data[ offset + 1 ] & 0xff ) << 8 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an unsigned short (16-bit) value from a byte array at a given
|
|
||||||
* offset. The value is converted to the opposed endian system while
|
|
||||||
* reading.
|
|
||||||
* @param data source byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @return the value read
|
|
||||||
*/
|
|
||||||
public static int readSwappedUnsignedShort(byte[] data, int offset) {
|
|
||||||
return ( ( ( data[ offset + 0 ] & 0xff ) << 0 ) +
|
|
||||||
( ( data[ offset + 1 ] & 0xff ) << 8 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "int" value to a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param data target byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @param value value to write
|
|
||||||
*/
|
|
||||||
public static void writeSwappedInteger(byte[] data, int offset, int value) {
|
|
||||||
data[ offset + 0 ] = (byte)( ( value >> 0 ) & 0xff );
|
|
||||||
data[ offset + 1 ] = (byte)( ( value >> 8 ) & 0xff );
|
|
||||||
data[ offset + 2 ] = (byte)( ( value >> 16 ) & 0xff );
|
|
||||||
data[ offset + 3 ] = (byte)( ( value >> 24 ) & 0xff );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "int" value from a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param data source byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @return the value read
|
|
||||||
*/
|
|
||||||
public static int readSwappedInteger(byte[] data, int offset) {
|
|
||||||
return ( ( ( data[ offset + 0 ] & 0xff ) << 0 ) +
|
|
||||||
( ( data[ offset + 1 ] & 0xff ) << 8 ) +
|
|
||||||
( ( data[ offset + 2 ] & 0xff ) << 16 ) +
|
|
||||||
( ( data[ offset + 3 ] & 0xff ) << 24 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an unsigned integer (32-bit) value from a byte array at a given
|
|
||||||
* offset. The value is converted to the opposed endian system while
|
|
||||||
* reading.
|
|
||||||
* @param data source byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @return the value read
|
|
||||||
*/
|
|
||||||
public static long readSwappedUnsignedInteger(byte[] data, int offset) {
|
|
||||||
long low = ( ( ( data[ offset + 0 ] & 0xff ) << 0 ) +
|
|
||||||
( ( data[ offset + 1 ] & 0xff ) << 8 ) +
|
|
||||||
( ( data[ offset + 2 ] & 0xff ) << 16 ) );
|
|
||||||
|
|
||||||
long high = data[ offset + 3 ] & 0xff;
|
|
||||||
|
|
||||||
return (high << 24) + (0xffffffffL & low);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "long" value to a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param data target byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @param value value to write
|
|
||||||
*/
|
|
||||||
public static void writeSwappedLong(byte[] data, int offset, long value) {
|
|
||||||
data[ offset + 0 ] = (byte)( ( value >> 0 ) & 0xff );
|
|
||||||
data[ offset + 1 ] = (byte)( ( value >> 8 ) & 0xff );
|
|
||||||
data[ offset + 2 ] = (byte)( ( value >> 16 ) & 0xff );
|
|
||||||
data[ offset + 3 ] = (byte)( ( value >> 24 ) & 0xff );
|
|
||||||
data[ offset + 4 ] = (byte)( ( value >> 32 ) & 0xff );
|
|
||||||
data[ offset + 5 ] = (byte)( ( value >> 40 ) & 0xff );
|
|
||||||
data[ offset + 6 ] = (byte)( ( value >> 48 ) & 0xff );
|
|
||||||
data[ offset + 7 ] = (byte)( ( value >> 56 ) & 0xff );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "long" value from a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param data source byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @return the value read
|
|
||||||
*/
|
|
||||||
public static long readSwappedLong(byte[] data, int offset) {
|
|
||||||
long low =
|
|
||||||
( ( data[ offset + 0 ] & 0xff ) << 0 ) +
|
|
||||||
( ( data[ offset + 1 ] & 0xff ) << 8 ) +
|
|
||||||
( ( data[ offset + 2 ] & 0xff ) << 16 ) +
|
|
||||||
( ( data[ offset + 3 ] & 0xff ) << 24 );
|
|
||||||
long high =
|
|
||||||
( ( data[ offset + 4 ] & 0xff ) << 0 ) +
|
|
||||||
( ( data[ offset + 5 ] & 0xff ) << 8 ) +
|
|
||||||
( ( data[ offset + 6 ] & 0xff ) << 16 ) +
|
|
||||||
( ( data[ offset + 7 ] & 0xff ) << 24 );
|
|
||||||
return (high << 32) + (0xffffffffL & low);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "float" value to a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param data target byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @param value value to write
|
|
||||||
*/
|
|
||||||
public static void writeSwappedFloat(byte[] data, int offset, float value) {
|
|
||||||
writeSwappedInteger( data, offset, Float.floatToIntBits( value ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "float" value from a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param data source byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @return the value read
|
|
||||||
*/
|
|
||||||
public static float readSwappedFloat(byte[] data, int offset) {
|
|
||||||
return Float.intBitsToFloat( readSwappedInteger( data, offset ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "double" value to a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param data target byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @param value value to write
|
|
||||||
*/
|
|
||||||
public static void writeSwappedDouble(byte[] data, int offset, double value) {
|
|
||||||
writeSwappedLong( data, offset, Double.doubleToLongBits( value ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "double" value from a byte array at a given offset. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param data source byte array
|
|
||||||
* @param offset starting offset in the byte array
|
|
||||||
* @return the value read
|
|
||||||
*/
|
|
||||||
public static double readSwappedDouble(byte[] data, int offset) {
|
|
||||||
return Double.longBitsToDouble( readSwappedLong( data, offset ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "short" value to an OutputStream. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param output target OutputStream
|
|
||||||
* @param value value to write
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void writeSwappedShort(OutputStream output, short value)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
output.write( (byte)( ( value >> 0 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 8 ) & 0xff ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "short" value from an InputStream. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param input source InputStream
|
|
||||||
* @return the value just read
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static short readSwappedShort(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
return (short)( ( ( read( input ) & 0xff ) << 0 ) +
|
|
||||||
( ( read( input ) & 0xff ) << 8 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a unsigned short (16-bit) from an InputStream. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param input source InputStream
|
|
||||||
* @return the value just read
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static int readSwappedUnsignedShort(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
int value1 = read( input );
|
|
||||||
int value2 = read( input );
|
|
||||||
|
|
||||||
return ( ( ( value1 & 0xff ) << 0 ) +
|
|
||||||
( ( value2 & 0xff ) << 8 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "int" value to an OutputStream. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param output target OutputStream
|
|
||||||
* @param value value to write
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void writeSwappedInteger(OutputStream output, int value)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
output.write( (byte)( ( value >> 0 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 8 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 16 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 24 ) & 0xff ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "int" value from an InputStream. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param input source InputStream
|
|
||||||
* @return the value just read
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static int readSwappedInteger(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
int value1 = read( input );
|
|
||||||
int value2 = read( input );
|
|
||||||
int value3 = read( input );
|
|
||||||
int value4 = read( input );
|
|
||||||
|
|
||||||
return ( ( value1 & 0xff ) << 0 ) +
|
|
||||||
( ( value2 & 0xff ) << 8 ) +
|
|
||||||
( ( value3 & 0xff ) << 16 ) +
|
|
||||||
( ( value4 & 0xff ) << 24 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a unsigned integer (32-bit) from an InputStream. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param input source InputStream
|
|
||||||
* @return the value just read
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static long readSwappedUnsignedInteger(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
int value1 = read( input );
|
|
||||||
int value2 = read( input );
|
|
||||||
int value3 = read( input );
|
|
||||||
int value4 = read( input );
|
|
||||||
|
|
||||||
long low = ( ( ( value1 & 0xff ) << 0 ) +
|
|
||||||
( ( value2 & 0xff ) << 8 ) +
|
|
||||||
( ( value3 & 0xff ) << 16 ) );
|
|
||||||
|
|
||||||
long high = value4 & 0xff;
|
|
||||||
|
|
||||||
return (high << 24) + (0xffffffffL & low);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "long" value to an OutputStream. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param output target OutputStream
|
|
||||||
* @param value value to write
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void writeSwappedLong(OutputStream output, long value)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
output.write( (byte)( ( value >> 0 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 8 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 16 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 24 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 32 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 40 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 48 ) & 0xff ) );
|
|
||||||
output.write( (byte)( ( value >> 56 ) & 0xff ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "long" value from an InputStream. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param input source InputStream
|
|
||||||
* @return the value just read
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static long readSwappedLong(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
byte[] bytes = new byte[8];
|
|
||||||
for ( int i=0; i<8; i++ ) {
|
|
||||||
bytes[i] = (byte) read( input );
|
|
||||||
}
|
|
||||||
return readSwappedLong( bytes, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "float" value to an OutputStream. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param output target OutputStream
|
|
||||||
* @param value value to write
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void writeSwappedFloat(OutputStream output, float value)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
writeSwappedInteger( output, Float.floatToIntBits( value ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "float" value from an InputStream. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param input source InputStream
|
|
||||||
* @return the value just read
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static float readSwappedFloat(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
return Float.intBitsToFloat( readSwappedInteger( input ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a "double" value to an OutputStream. The value is
|
|
||||||
* converted to the opposed endian system while writing.
|
|
||||||
* @param output target OutputStream
|
|
||||||
* @param value value to write
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static void writeSwappedDouble(OutputStream output, double value)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
writeSwappedLong( output, Double.doubleToLongBits( value ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a "double" value from an InputStream. The value is
|
|
||||||
* converted to the opposed endian system while reading.
|
|
||||||
* @param input source InputStream
|
|
||||||
* @return the value just read
|
|
||||||
* @throws IOException in case of an I/O problem
|
|
||||||
*/
|
|
||||||
public static double readSwappedDouble(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
return Double.longBitsToDouble( readSwappedLong( input ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the next byte from the input stream.
|
|
||||||
* @param input the stream
|
|
||||||
* @return the byte
|
|
||||||
* @throws IOException if the end of file is reached
|
|
||||||
*/
|
|
||||||
private static int read(InputStream input)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
int value = input.read();
|
|
||||||
|
|
||||||
if( -1 == value ) {
|
|
||||||
throw new EOFException( "Unexpected EOF reached" );
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keeps track of files awaiting deletion, and deletes them when an associated
|
|
||||||
* marker object is reclaimed by the garbage collector.
|
|
||||||
* <p>
|
|
||||||
* This utility creates a background thread to handle file deletion.
|
|
||||||
* Each file to be deleted is registered with a handler object.
|
|
||||||
* When the handler object is garbage collected, the file is deleted.
|
|
||||||
* <p>
|
|
||||||
* In an environment with multiple class loaders (a servlet container, for
|
|
||||||
* example), you should consider stopping the background thread if it is no
|
|
||||||
* longer needed. This is done by invoking the method
|
|
||||||
* {@link #exitWhenFinished}, typically in
|
|
||||||
* {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
|
|
||||||
*
|
|
||||||
* @author Noel Bergman
|
|
||||||
* @author Martin Cooper
|
|
||||||
* @version $Id: FileCleaner.java 553012 2007-07-03 23:01:07Z ggregory $
|
|
||||||
* @deprecated Use {@link FileCleaningTracker}
|
|
||||||
*/
|
|
||||||
public class FileCleaner {
|
|
||||||
/**
|
|
||||||
* The instance to use for the deprecated, static methods.
|
|
||||||
*/
|
|
||||||
static final FileCleaningTracker theInstance = new FileCleaningTracker();
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
|
|
||||||
*
|
|
||||||
* @param file the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @throws NullPointerException if the file is null
|
|
||||||
* @deprecated Use {@link FileCleaningTracker#track(File, Object)}.
|
|
||||||
*/
|
|
||||||
public static void track(File file, Object marker) {
|
|
||||||
theInstance.track(file, marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The speified deletion strategy is used.
|
|
||||||
*
|
|
||||||
* @param file the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @param deleteStrategy the strategy to delete the file, null means normal
|
|
||||||
* @throws NullPointerException if the file is null
|
|
||||||
* @deprecated Use {@link FileCleaningTracker#track(File, Object, FileDeleteStrategy)}.
|
|
||||||
*/
|
|
||||||
public static void track(File file, Object marker, FileDeleteStrategy deleteStrategy) {
|
|
||||||
theInstance.track(file, marker, deleteStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
|
|
||||||
*
|
|
||||||
* @param path the full path to the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @throws NullPointerException if the path is null
|
|
||||||
* @deprecated Use {@link FileCleaningTracker#track(String, Object)}.
|
|
||||||
*/
|
|
||||||
public static void track(String path, Object marker) {
|
|
||||||
theInstance.track(path, marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The speified deletion strategy is used.
|
|
||||||
*
|
|
||||||
* @param path the full path to the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @param deleteStrategy the strategy to delete the file, null means normal
|
|
||||||
* @throws NullPointerException if the path is null
|
|
||||||
* @deprecated Use {@link FileCleaningTracker#track(String, Object, FileDeleteStrategy)}.
|
|
||||||
*/
|
|
||||||
public static void track(String path, Object marker, FileDeleteStrategy deleteStrategy) {
|
|
||||||
theInstance.track(path, marker, deleteStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Retrieve the number of files currently being tracked, and therefore
|
|
||||||
* awaiting deletion.
|
|
||||||
*
|
|
||||||
* @return the number of files being tracked
|
|
||||||
* @deprecated Use {@link FileCleaningTracker#getTrackCount()}.
|
|
||||||
*/
|
|
||||||
public static int getTrackCount() {
|
|
||||||
return theInstance.getTrackCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this method to cause the file cleaner thread to terminate when
|
|
||||||
* there are no more objects being tracked for deletion.
|
|
||||||
* <p>
|
|
||||||
* In a simple environment, you don't need this method as the file cleaner
|
|
||||||
* thread will simply exit when the JVM exits. In a more complex environment,
|
|
||||||
* with multiple class loaders (such as an application server), you should be
|
|
||||||
* aware that the file cleaner thread will continue running even if the class
|
|
||||||
* loader it was started from terminates. This can consitute a memory leak.
|
|
||||||
* <p>
|
|
||||||
* For example, suppose that you have developed a web application, which
|
|
||||||
* contains the commons-io jar file in your WEB-INF/lib directory. In other
|
|
||||||
* words, the FileCleaner class is loaded through the class loader of your
|
|
||||||
* web application. If the web application is terminated, but the servlet
|
|
||||||
* container is still running, then the file cleaner thread will still exist,
|
|
||||||
* posing a memory leak.
|
|
||||||
* <p>
|
|
||||||
* This method allows the thread to be terminated. Simply call this method
|
|
||||||
* in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
|
|
||||||
* One called, no new objects can be tracked by the file cleaner.
|
|
||||||
* @deprecated Use {@link FileCleaningTracker#exitWhenFinished()}.
|
|
||||||
*/
|
|
||||||
public static synchronized void exitWhenFinished() {
|
|
||||||
theInstance.exitWhenFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the singleton instance, which is used by the deprecated, static methods.
|
|
||||||
* This is mainly useful for code, which wants to support the new
|
|
||||||
* {@link FileCleaningTracker} class while maintain compatibility with the
|
|
||||||
* deprecated {@link FileCleaner}.
|
|
||||||
*
|
|
||||||
* @return the singleton instance
|
|
||||||
*/
|
|
||||||
public static FileCleaningTracker getInstance() {
|
|
||||||
return theInstance;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,258 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.ref.PhantomReference;
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keeps track of files awaiting deletion, and deletes them when an associated
|
|
||||||
* marker object is reclaimed by the garbage collector.
|
|
||||||
* <p>
|
|
||||||
* This utility creates a background thread to handle file deletion.
|
|
||||||
* Each file to be deleted is registered with a handler object.
|
|
||||||
* When the handler object is garbage collected, the file is deleted.
|
|
||||||
* <p>
|
|
||||||
* In an environment with multiple class loaders (a servlet container, for
|
|
||||||
* example), you should consider stopping the background thread if it is no
|
|
||||||
* longer needed. This is done by invoking the method
|
|
||||||
* {@link #exitWhenFinished}, typically in
|
|
||||||
* {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
|
|
||||||
*
|
|
||||||
* @author Noel Bergman
|
|
||||||
* @author Martin Cooper
|
|
||||||
* @version $Id: FileCleaner.java 490987 2006-12-29 12:11:48Z scolebourne $
|
|
||||||
*/
|
|
||||||
public class FileCleaningTracker {
|
|
||||||
/**
|
|
||||||
* Queue of <code>Tracker</code> instances being watched.
|
|
||||||
*/
|
|
||||||
ReferenceQueue /* Tracker */ q = new ReferenceQueue();
|
|
||||||
/**
|
|
||||||
* Collection of <code>Tracker</code> instances in existence.
|
|
||||||
*/
|
|
||||||
final Collection /* Tracker */ trackers = new Vector(); // synchronized
|
|
||||||
/**
|
|
||||||
* Whether to terminate the thread when the tracking is complete.
|
|
||||||
*/
|
|
||||||
volatile boolean exitWhenFinished = false;
|
|
||||||
/**
|
|
||||||
* The thread that will clean up registered files.
|
|
||||||
*/
|
|
||||||
Thread reaper;
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
|
|
||||||
*
|
|
||||||
* @param file the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @throws NullPointerException if the file is null
|
|
||||||
*/
|
|
||||||
public void track(File file, Object marker) {
|
|
||||||
track(file, marker, (FileDeleteStrategy) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The speified deletion strategy is used.
|
|
||||||
*
|
|
||||||
* @param file the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @param deleteStrategy the strategy to delete the file, null means normal
|
|
||||||
* @throws NullPointerException if the file is null
|
|
||||||
*/
|
|
||||||
public void track(File file, Object marker, FileDeleteStrategy deleteStrategy) {
|
|
||||||
if (file == null) {
|
|
||||||
throw new NullPointerException("The file must not be null");
|
|
||||||
}
|
|
||||||
addTracker(file.getPath(), marker, deleteStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
|
|
||||||
*
|
|
||||||
* @param path the full path to the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @throws NullPointerException if the path is null
|
|
||||||
*/
|
|
||||||
public void track(String path, Object marker) {
|
|
||||||
track(path, marker, (FileDeleteStrategy) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track the specified file, using the provided marker, deleting the file
|
|
||||||
* when the marker instance is garbage collected.
|
|
||||||
* The speified deletion strategy is used.
|
|
||||||
*
|
|
||||||
* @param path the full path to the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @param deleteStrategy the strategy to delete the file, null means normal
|
|
||||||
* @throws NullPointerException if the path is null
|
|
||||||
*/
|
|
||||||
public void track(String path, Object marker, FileDeleteStrategy deleteStrategy) {
|
|
||||||
if (path == null) {
|
|
||||||
throw new NullPointerException("The path must not be null");
|
|
||||||
}
|
|
||||||
addTracker(path, marker, deleteStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a tracker to the list of trackers.
|
|
||||||
*
|
|
||||||
* @param path the full path to the file to be tracked, not null
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @param deleteStrategy the strategy to delete the file, null means normal
|
|
||||||
*/
|
|
||||||
private synchronized void addTracker(String path, Object marker, FileDeleteStrategy deleteStrategy) {
|
|
||||||
// synchronized block protects reaper
|
|
||||||
if (exitWhenFinished) {
|
|
||||||
throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
|
|
||||||
}
|
|
||||||
if (reaper == null) {
|
|
||||||
reaper = new Reaper();
|
|
||||||
reaper.start();
|
|
||||||
}
|
|
||||||
trackers.add(new Tracker(path, deleteStrategy, marker, q));
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Retrieve the number of files currently being tracked, and therefore
|
|
||||||
* awaiting deletion.
|
|
||||||
*
|
|
||||||
* @return the number of files being tracked
|
|
||||||
*/
|
|
||||||
public int getTrackCount() {
|
|
||||||
return trackers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this method to cause the file cleaner thread to terminate when
|
|
||||||
* there are no more objects being tracked for deletion.
|
|
||||||
* <p>
|
|
||||||
* In a simple environment, you don't need this method as the file cleaner
|
|
||||||
* thread will simply exit when the JVM exits. In a more complex environment,
|
|
||||||
* with multiple class loaders (such as an application server), you should be
|
|
||||||
* aware that the file cleaner thread will continue running even if the class
|
|
||||||
* loader it was started from terminates. This can consitute a memory leak.
|
|
||||||
* <p>
|
|
||||||
* For example, suppose that you have developed a web application, which
|
|
||||||
* contains the commons-io jar file in your WEB-INF/lib directory. In other
|
|
||||||
* words, the FileCleaner class is loaded through the class loader of your
|
|
||||||
* web application. If the web application is terminated, but the servlet
|
|
||||||
* container is still running, then the file cleaner thread will still exist,
|
|
||||||
* posing a memory leak.
|
|
||||||
* <p>
|
|
||||||
* This method allows the thread to be terminated. Simply call this method
|
|
||||||
* in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
|
|
||||||
* One called, no new objects can be tracked by the file cleaner.
|
|
||||||
*/
|
|
||||||
public synchronized void exitWhenFinished() {
|
|
||||||
// synchronized block protects reaper
|
|
||||||
exitWhenFinished = true;
|
|
||||||
if (reaper != null) {
|
|
||||||
synchronized (reaper) {
|
|
||||||
reaper.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* The reaper thread.
|
|
||||||
*/
|
|
||||||
private final class Reaper extends Thread {
|
|
||||||
/** Construct a new Reaper */
|
|
||||||
Reaper() {
|
|
||||||
super("File Reaper");
|
|
||||||
setPriority(Thread.MAX_PRIORITY);
|
|
||||||
setDaemon(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the reaper thread that will delete files as their associated
|
|
||||||
* marker objects are reclaimed by the garbage collector.
|
|
||||||
*/
|
|
||||||
public void run() {
|
|
||||||
// thread exits when exitWhenFinished is true and there are no more tracked objects
|
|
||||||
while (exitWhenFinished == false || trackers.size() > 0) {
|
|
||||||
Tracker tracker = null;
|
|
||||||
try {
|
|
||||||
// Wait for a tracker to remove.
|
|
||||||
tracker = (Tracker) q.remove();
|
|
||||||
} catch (Exception e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (tracker != null) {
|
|
||||||
tracker.delete();
|
|
||||||
tracker.clear();
|
|
||||||
trackers.remove(tracker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Inner class which acts as the reference for a file pending deletion.
|
|
||||||
*/
|
|
||||||
private static final class Tracker extends PhantomReference {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The full path to the file being tracked.
|
|
||||||
*/
|
|
||||||
private final String path;
|
|
||||||
/**
|
|
||||||
* The strategy for deleting files.
|
|
||||||
*/
|
|
||||||
private final FileDeleteStrategy deleteStrategy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance of this class from the supplied parameters.
|
|
||||||
*
|
|
||||||
* @param path the full path to the file to be tracked, not null
|
|
||||||
* @param deleteStrategy the strategy to delete the file, null means normal
|
|
||||||
* @param marker the marker object used to track the file, not null
|
|
||||||
* @param queue the queue on to which the tracker will be pushed, not null
|
|
||||||
*/
|
|
||||||
Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue queue) {
|
|
||||||
super(marker, queue);
|
|
||||||
this.path = path;
|
|
||||||
this.deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the file associated with this tracker instance.
|
|
||||||
*
|
|
||||||
* @return <code>true</code> if the file was deleted successfully;
|
|
||||||
* <code>false</code> otherwise.
|
|
||||||
*/
|
|
||||||
public boolean delete() {
|
|
||||||
return deleteStrategy.deleteQuietly(new File(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strategy for deleting files.
|
|
||||||
* <p>
|
|
||||||
* There is more than one way to delete a file.
|
|
||||||
* You may want to limit access to certain directories, to only delete
|
|
||||||
* directories if they are empty, or maybe to force deletion.
|
|
||||||
* <p>
|
|
||||||
* This class captures the strategy to use and is designed for user subclassing.
|
|
||||||
*
|
|
||||||
* @author Stephen Colebourne
|
|
||||||
* @version $Id: FileDeleteStrategy.java 453903 2006-10-07 13:47:06Z scolebourne $
|
|
||||||
* @since Commons IO 1.3
|
|
||||||
*/
|
|
||||||
public class FileDeleteStrategy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The singleton instance for normal file deletion, which does not permit
|
|
||||||
* the deletion of directories that are not empty.
|
|
||||||
*/
|
|
||||||
public static final FileDeleteStrategy NORMAL = new FileDeleteStrategy("Normal");
|
|
||||||
/**
|
|
||||||
* The singleton instance for forced file deletion, which always deletes,
|
|
||||||
* even if the file represents a non-empty directory.
|
|
||||||
*/
|
|
||||||
public static final FileDeleteStrategy FORCE = new ForceFileDeleteStrategy();
|
|
||||||
|
|
||||||
/** The name of the strategy. */
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Restricted constructor.
|
|
||||||
*
|
|
||||||
* @param name the name by which the strategy is known
|
|
||||||
*/
|
|
||||||
protected FileDeleteStrategy(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Deletes the file object, which may be a file or a directory.
|
|
||||||
* All <code>IOException</code>s are caught and false returned instead.
|
|
||||||
* If the file does not exist or is null, true is returned.
|
|
||||||
* <p>
|
|
||||||
* Subclass writers should override {@link #doDelete(File)}, not this method.
|
|
||||||
*
|
|
||||||
* @param fileToDelete the file to delete, null returns true
|
|
||||||
* @return true if the file was deleted, or there was no such file
|
|
||||||
*/
|
|
||||||
public boolean deleteQuietly(File fileToDelete) {
|
|
||||||
if (fileToDelete == null || fileToDelete.exists() == false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return doDelete(fileToDelete);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the file object, which may be a file or a directory.
|
|
||||||
* If the file does not exist, the method just returns.
|
|
||||||
* <p>
|
|
||||||
* Subclass writers should override {@link #doDelete(File)}, not this method.
|
|
||||||
*
|
|
||||||
* @param fileToDelete the file to delete, not null
|
|
||||||
* @throws NullPointerException if the file is null
|
|
||||||
* @throws IOException if an error occurs during file deletion
|
|
||||||
*/
|
|
||||||
public void delete(File fileToDelete) throws IOException {
|
|
||||||
if (fileToDelete.exists() && doDelete(fileToDelete) == false) {
|
|
||||||
throw new IOException("Deletion failed: " + fileToDelete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actually deletes the file object, which may be a file or a directory.
|
|
||||||
* <p>
|
|
||||||
* This method is designed for subclasses to override.
|
|
||||||
* The implementation may return either false or an <code>IOException</code>
|
|
||||||
* when deletion fails. The {@link #delete(File)} and {@link #deleteQuietly(File)}
|
|
||||||
* methods will handle either response appropriately.
|
|
||||||
* A check has been made to ensure that the file will exist.
|
|
||||||
* <p>
|
|
||||||
* This implementation uses {@link File#delete()}.
|
|
||||||
*
|
|
||||||
* @param fileToDelete the file to delete, exists, not null
|
|
||||||
* @return true if the file was deleteds
|
|
||||||
* @throws NullPointerException if the file is null
|
|
||||||
* @throws IOException if an error occurs during file deletion
|
|
||||||
*/
|
|
||||||
protected boolean doDelete(File fileToDelete) throws IOException {
|
|
||||||
return fileToDelete.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Gets a string describing the delete strategy.
|
|
||||||
*
|
|
||||||
* @return a string describing the delete strategy
|
|
||||||
*/
|
|
||||||
public String toString() {
|
|
||||||
return "FileDeleteStrategy[" + name + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Force file deletion strategy.
|
|
||||||
*/
|
|
||||||
static class ForceFileDeleteStrategy extends FileDeleteStrategy {
|
|
||||||
/** Default Constructor */
|
|
||||||
ForceFileDeleteStrategy() {
|
|
||||||
super("Force");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the file object.
|
|
||||||
* <p>
|
|
||||||
* This implementation uses <code>FileUtils.forceDelete() <code>
|
|
||||||
* if the file exists.
|
|
||||||
*
|
|
||||||
* @param fileToDelete the file to delete, not null
|
|
||||||
* @return Always returns <code>true</code>
|
|
||||||
* @throws NullPointerException if the file is null
|
|
||||||
* @throws IOException if an error occurs during file deletion
|
|
||||||
*/
|
|
||||||
protected boolean doDelete(File fileToDelete) throws IOException {
|
|
||||||
FileUtils.forceDelete(fileToDelete);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,457 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* General File System utilities.
|
|
||||||
* <p>
|
|
||||||
* This class provides static utility methods for general file system
|
|
||||||
* functions not provided via the JDK {@link java.io.File File} class.
|
|
||||||
* <p>
|
|
||||||
* The current functions provided are:
|
|
||||||
* <ul>
|
|
||||||
* <li>Get the free space on a drive
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @author Frank W. Zammetti
|
|
||||||
* @author Stephen Colebourne
|
|
||||||
* @author Thomas Ledoux
|
|
||||||
* @author James Urie
|
|
||||||
* @author Magnus Grimsell
|
|
||||||
* @author Thomas Ledoux
|
|
||||||
* @version $Id: FileSystemUtils.java 453889 2006-10-07 11:56:25Z scolebourne $
|
|
||||||
* @since Commons IO 1.1
|
|
||||||
*/
|
|
||||||
public class FileSystemUtils {
|
|
||||||
|
|
||||||
/** Singleton instance, used mainly for testing. */
|
|
||||||
private static final FileSystemUtils INSTANCE = new FileSystemUtils();
|
|
||||||
|
|
||||||
/** Operating system state flag for error. */
|
|
||||||
private static final int INIT_PROBLEM = -1;
|
|
||||||
/** Operating system state flag for neither Unix nor Windows. */
|
|
||||||
private static final int OTHER = 0;
|
|
||||||
/** Operating system state flag for Windows. */
|
|
||||||
private static final int WINDOWS = 1;
|
|
||||||
/** Operating system state flag for Unix. */
|
|
||||||
private static final int UNIX = 2;
|
|
||||||
/** Operating system state flag for Posix flavour Unix. */
|
|
||||||
private static final int POSIX_UNIX = 3;
|
|
||||||
|
|
||||||
/** The operating system flag. */
|
|
||||||
private static final int OS;
|
|
||||||
static {
|
|
||||||
int os = OTHER;
|
|
||||||
try {
|
|
||||||
String osName = System.getProperty("os.name");
|
|
||||||
if (osName == null) {
|
|
||||||
throw new IOException("os.name not found");
|
|
||||||
}
|
|
||||||
osName = osName.toLowerCase();
|
|
||||||
// match
|
|
||||||
if (osName.indexOf("windows") != -1) {
|
|
||||||
os = WINDOWS;
|
|
||||||
} else if (osName.indexOf("linux") != -1 ||
|
|
||||||
osName.indexOf("sun os") != -1 ||
|
|
||||||
osName.indexOf("sunos") != -1 ||
|
|
||||||
osName.indexOf("solaris") != -1 ||
|
|
||||||
osName.indexOf("mpe/ix") != -1 ||
|
|
||||||
osName.indexOf("freebsd") != -1 ||
|
|
||||||
osName.indexOf("irix") != -1 ||
|
|
||||||
osName.indexOf("digital unix") != -1 ||
|
|
||||||
osName.indexOf("unix") != -1 ||
|
|
||||||
osName.indexOf("mac os x") != -1) {
|
|
||||||
os = UNIX;
|
|
||||||
} else if (osName.indexOf("hp-ux") != -1 ||
|
|
||||||
osName.indexOf("aix") != -1) {
|
|
||||||
os = POSIX_UNIX;
|
|
||||||
} else {
|
|
||||||
os = OTHER;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
os = INIT_PROBLEM;
|
|
||||||
}
|
|
||||||
OS = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instances should NOT be constructed in standard programming.
|
|
||||||
*/
|
|
||||||
public FileSystemUtils() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Returns the free space on a drive or volume by invoking
|
|
||||||
* the command line.
|
|
||||||
* This method does not normalize the result, and typically returns
|
|
||||||
* bytes on Windows, 512 byte units on OS X and kilobytes on Unix.
|
|
||||||
* As this is not very useful, this method is deprecated in favour
|
|
||||||
* of {@link #freeSpaceKb(String)} which returns a result in kilobytes.
|
|
||||||
* <p>
|
|
||||||
* Note that some OS's are NOT currently supported, including OS/390,
|
|
||||||
* OpenVMS and and SunOS 5. (SunOS is supported by <code>freeSpaceKb</code>.)
|
|
||||||
* <pre>
|
|
||||||
* FileSystemUtils.freeSpace("C:"); // Windows
|
|
||||||
* FileSystemUtils.freeSpace("/volume"); // *nix
|
|
||||||
* </pre>
|
|
||||||
* The free space is calculated via the command line.
|
|
||||||
* It uses 'dir /-c' on Windows and 'df' on *nix.
|
|
||||||
*
|
|
||||||
* @param path the path to get free space for, not null, not empty on Unix
|
|
||||||
* @return the amount of free drive space on the drive or volume
|
|
||||||
* @throws IllegalArgumentException if the path is invalid
|
|
||||||
* @throws IllegalStateException if an error occurred in initialisation
|
|
||||||
* @throws IOException if an error occurs when finding the free space
|
|
||||||
* @since Commons IO 1.1, enhanced OS support in 1.2 and 1.3
|
|
||||||
* @deprecated Use freeSpaceKb(String)
|
|
||||||
* Deprecated from 1.3, may be removed in 2.0
|
|
||||||
*/
|
|
||||||
public static long freeSpace(String path) throws IOException {
|
|
||||||
return INSTANCE.freeSpaceOS(path, OS, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Returns the free space on a drive or volume in kilobytes by invoking
|
|
||||||
* the command line.
|
|
||||||
* <pre>
|
|
||||||
* FileSystemUtils.freeSpaceKb("C:"); // Windows
|
|
||||||
* FileSystemUtils.freeSpaceKb("/volume"); // *nix
|
|
||||||
* </pre>
|
|
||||||
* The free space is calculated via the command line.
|
|
||||||
* It uses 'dir /-c' on Windows, 'df -kP' on AIX/HP-UX and 'df -k' on other Unix.
|
|
||||||
* <p>
|
|
||||||
* In order to work, you must be running Windows, or have a implementation of
|
|
||||||
* Unix df that supports GNU format when passed -k (or -kP). If you are going
|
|
||||||
* to rely on this code, please check that it works on your OS by running
|
|
||||||
* some simple tests to compare the command line with the output from this class.
|
|
||||||
* If your operating system isn't supported, please raise a JIRA call detailing
|
|
||||||
* the exact result from df -k and as much other detail as possible, thanks.
|
|
||||||
*
|
|
||||||
* @param path the path to get free space for, not null, not empty on Unix
|
|
||||||
* @return the amount of free drive space on the drive or volume in kilobytes
|
|
||||||
* @throws IllegalArgumentException if the path is invalid
|
|
||||||
* @throws IllegalStateException if an error occurred in initialisation
|
|
||||||
* @throws IOException if an error occurs when finding the free space
|
|
||||||
* @since Commons IO 1.2, enhanced OS support in 1.3
|
|
||||||
*/
|
|
||||||
public static long freeSpaceKb(String path) throws IOException {
|
|
||||||
return INSTANCE.freeSpaceOS(path, OS, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Returns the free space on a drive or volume in a cross-platform manner.
|
|
||||||
* Note that some OS's are NOT currently supported, including OS/390.
|
|
||||||
* <pre>
|
|
||||||
* FileSystemUtils.freeSpace("C:"); // Windows
|
|
||||||
* FileSystemUtils.freeSpace("/volume"); // *nix
|
|
||||||
* </pre>
|
|
||||||
* The free space is calculated via the command line.
|
|
||||||
* It uses 'dir /-c' on Windows and 'df' on *nix.
|
|
||||||
*
|
|
||||||
* @param path the path to get free space for, not null, not empty on Unix
|
|
||||||
* @param os the operating system code
|
|
||||||
* @param kb whether to normalize to kilobytes
|
|
||||||
* @return the amount of free drive space on the drive or volume
|
|
||||||
* @throws IllegalArgumentException if the path is invalid
|
|
||||||
* @throws IllegalStateException if an error occurred in initialisation
|
|
||||||
* @throws IOException if an error occurs when finding the free space
|
|
||||||
*/
|
|
||||||
long freeSpaceOS(String path, int os, boolean kb) throws IOException {
|
|
||||||
if (path == null) {
|
|
||||||
throw new IllegalArgumentException("Path must not be empty");
|
|
||||||
}
|
|
||||||
switch (os) {
|
|
||||||
case WINDOWS:
|
|
||||||
return (kb ? freeSpaceWindows(path) / 1024 : freeSpaceWindows(path));
|
|
||||||
case UNIX:
|
|
||||||
return freeSpaceUnix(path, kb, false);
|
|
||||||
case POSIX_UNIX:
|
|
||||||
return freeSpaceUnix(path, kb, true);
|
|
||||||
case OTHER:
|
|
||||||
throw new IllegalStateException("Unsupported operating system");
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Exception caught when determining operating system");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Find free space on the Windows platform using the 'dir' command.
|
|
||||||
*
|
|
||||||
* @param path the path to get free space for, including the colon
|
|
||||||
* @return the amount of free drive space on the drive
|
|
||||||
* @throws IOException if an error occurs
|
|
||||||
*/
|
|
||||||
long freeSpaceWindows(String path) throws IOException {
|
|
||||||
path = FilenameUtils.normalize(path);
|
|
||||||
if (path.length() > 2 && path.charAt(1) == ':') {
|
|
||||||
path = path.substring(0, 2); // seems to make it work
|
|
||||||
}
|
|
||||||
|
|
||||||
// build and run the 'dir' command
|
|
||||||
String[] cmdAttribs = new String[] {"cmd.exe", "/C", "dir /-c " + path};
|
|
||||||
|
|
||||||
// read in the output of the command to an ArrayList
|
|
||||||
List lines = performCommand(cmdAttribs, Integer.MAX_VALUE);
|
|
||||||
|
|
||||||
// now iterate over the lines we just read and find the LAST
|
|
||||||
// non-empty line (the free space bytes should be in the last element
|
|
||||||
// of the ArrayList anyway, but this will ensure it works even if it's
|
|
||||||
// not, still assuming it is on the last non-blank line)
|
|
||||||
for (int i = lines.size() - 1; i >= 0; i--) {
|
|
||||||
String line = (String) lines.get(i);
|
|
||||||
if (line.length() > 0) {
|
|
||||||
return parseDir(line, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// all lines are blank
|
|
||||||
throw new IOException(
|
|
||||||
"Command line 'dir /-c' did not return any info " +
|
|
||||||
"for path '" + path + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the Windows dir response last line
|
|
||||||
*
|
|
||||||
* @param line the line to parse
|
|
||||||
* @param path the path that was sent
|
|
||||||
* @return the number of bytes
|
|
||||||
* @throws IOException if an error occurs
|
|
||||||
*/
|
|
||||||
long parseDir(String line, String path) throws IOException {
|
|
||||||
// read from the end of the line to find the last numeric
|
|
||||||
// character on the line, then continue until we find the first
|
|
||||||
// non-numeric character, and everything between that and the last
|
|
||||||
// numeric character inclusive is our free space bytes count
|
|
||||||
int bytesStart = 0;
|
|
||||||
int bytesEnd = 0;
|
|
||||||
int j = line.length() - 1;
|
|
||||||
innerLoop1: while (j >= 0) {
|
|
||||||
char c = line.charAt(j);
|
|
||||||
if (Character.isDigit(c)) {
|
|
||||||
// found the last numeric character, this is the end of
|
|
||||||
// the free space bytes count
|
|
||||||
bytesEnd = j + 1;
|
|
||||||
break innerLoop1;
|
|
||||||
}
|
|
||||||
j--;
|
|
||||||
}
|
|
||||||
innerLoop2: while (j >= 0) {
|
|
||||||
char c = line.charAt(j);
|
|
||||||
if (!Character.isDigit(c) && c != ',' && c != '.') {
|
|
||||||
// found the next non-numeric character, this is the
|
|
||||||
// beginning of the free space bytes count
|
|
||||||
bytesStart = j + 1;
|
|
||||||
break innerLoop2;
|
|
||||||
}
|
|
||||||
j--;
|
|
||||||
}
|
|
||||||
if (j < 0) {
|
|
||||||
throw new IOException(
|
|
||||||
"Command line 'dir /-c' did not return valid info " +
|
|
||||||
"for path '" + path + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove commas and dots in the bytes count
|
|
||||||
StringBuffer buf = new StringBuffer(line.substring(bytesStart, bytesEnd));
|
|
||||||
for (int k = 0; k < buf.length(); k++) {
|
|
||||||
if (buf.charAt(k) == ',' || buf.charAt(k) == '.') {
|
|
||||||
buf.deleteCharAt(k--);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parseBytes(buf.toString(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Find free space on the *nix platform using the 'df' command.
|
|
||||||
*
|
|
||||||
* @param path the path to get free space for
|
|
||||||
* @param kb whether to normalize to kilobytes
|
|
||||||
* @param posix whether to use the posix standard format flag
|
|
||||||
* @return the amount of free drive space on the volume
|
|
||||||
* @throws IOException if an error occurs
|
|
||||||
*/
|
|
||||||
long freeSpaceUnix(String path, boolean kb, boolean posix) throws IOException {
|
|
||||||
if (path.length() == 0) {
|
|
||||||
throw new IllegalArgumentException("Path must not be empty");
|
|
||||||
}
|
|
||||||
path = FilenameUtils.normalize(path);
|
|
||||||
|
|
||||||
// build and run the 'dir' command
|
|
||||||
String flags = "-";
|
|
||||||
if (kb) {
|
|
||||||
flags += "k";
|
|
||||||
}
|
|
||||||
if (posix) {
|
|
||||||
flags += "P";
|
|
||||||
}
|
|
||||||
String[] cmdAttribs =
|
|
||||||
(flags.length() > 1 ? new String[] {"df", flags, path} : new String[] {"df", path});
|
|
||||||
|
|
||||||
// perform the command, asking for up to 3 lines (header, interesting, overflow)
|
|
||||||
List lines = performCommand(cmdAttribs, 3);
|
|
||||||
if (lines.size() < 2) {
|
|
||||||
// unknown problem, throw exception
|
|
||||||
throw new IOException(
|
|
||||||
"Command line 'df' did not return info as expected " +
|
|
||||||
"for path '" + path + "'- response was " + lines);
|
|
||||||
}
|
|
||||||
String line2 = (String) lines.get(1); // the line we're interested in
|
|
||||||
|
|
||||||
// Now, we tokenize the string. The fourth element is what we want.
|
|
||||||
StringTokenizer tok = new StringTokenizer(line2, " ");
|
|
||||||
if (tok.countTokens() < 4) {
|
|
||||||
// could be long Filesystem, thus data on third line
|
|
||||||
if (tok.countTokens() == 1 && lines.size() >= 3) {
|
|
||||||
String line3 = (String) lines.get(2); // the line may be interested in
|
|
||||||
tok = new StringTokenizer(line3, " ");
|
|
||||||
} else {
|
|
||||||
throw new IOException(
|
|
||||||
"Command line 'df' did not return data as expected " +
|
|
||||||
"for path '" + path + "'- check path is valid");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tok.nextToken(); // Ignore Filesystem
|
|
||||||
}
|
|
||||||
tok.nextToken(); // Ignore 1K-blocks
|
|
||||||
tok.nextToken(); // Ignore Used
|
|
||||||
String freeSpace = tok.nextToken();
|
|
||||||
return parseBytes(freeSpace, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Parses the bytes from a string.
|
|
||||||
*
|
|
||||||
* @param freeSpace the free space string
|
|
||||||
* @param path the path
|
|
||||||
* @return the number of bytes
|
|
||||||
* @throws IOException if an error occurs
|
|
||||||
*/
|
|
||||||
long parseBytes(String freeSpace, String path) throws IOException {
|
|
||||||
try {
|
|
||||||
long bytes = Long.parseLong(freeSpace);
|
|
||||||
if (bytes < 0) {
|
|
||||||
throw new IOException(
|
|
||||||
"Command line 'df' did not find free space in response " +
|
|
||||||
"for path '" + path + "'- check path is valid");
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new IOException(
|
|
||||||
"Command line 'df' did not return numeric data as expected " +
|
|
||||||
"for path '" + path + "'- check path is valid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Performs the os command.
|
|
||||||
*
|
|
||||||
* @param cmdAttribs the command line parameters
|
|
||||||
* @param max The maximum limit for the lines returned
|
|
||||||
* @return the parsed data
|
|
||||||
* @throws IOException if an error occurs
|
|
||||||
*/
|
|
||||||
List performCommand(String[] cmdAttribs, int max) throws IOException {
|
|
||||||
// this method does what it can to avoid the 'Too many open files' error
|
|
||||||
// based on trial and error and these links:
|
|
||||||
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692
|
|
||||||
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4801027
|
|
||||||
// http://forum.java.sun.com/thread.jspa?threadID=533029&messageID=2572018
|
|
||||||
// however, its still not perfect as the JDK support is so poor
|
|
||||||
// (see commond-exec or ant for a better multi-threaded multi-os solution)
|
|
||||||
|
|
||||||
List lines = new ArrayList(20);
|
|
||||||
Process proc = null;
|
|
||||||
InputStream in = null;
|
|
||||||
OutputStream out = null;
|
|
||||||
InputStream err = null;
|
|
||||||
BufferedReader inr = null;
|
|
||||||
try {
|
|
||||||
proc = openProcess(cmdAttribs);
|
|
||||||
in = proc.getInputStream();
|
|
||||||
out = proc.getOutputStream();
|
|
||||||
err = proc.getErrorStream();
|
|
||||||
inr = new BufferedReader(new InputStreamReader(in));
|
|
||||||
String line = inr.readLine();
|
|
||||||
while (line != null && lines.size() < max) {
|
|
||||||
line = line.toLowerCase().trim();
|
|
||||||
lines.add(line);
|
|
||||||
line = inr.readLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.waitFor();
|
|
||||||
if (proc.exitValue() != 0) {
|
|
||||||
// os command problem, throw exception
|
|
||||||
throw new IOException(
|
|
||||||
"Command line returned OS error code '" + proc.exitValue() +
|
|
||||||
"' for command " + Arrays.asList(cmdAttribs));
|
|
||||||
}
|
|
||||||
if (lines.size() == 0) {
|
|
||||||
// unknown problem, throw exception
|
|
||||||
throw new IOException(
|
|
||||||
"Command line did not return any info " +
|
|
||||||
"for command " + Arrays.asList(cmdAttribs));
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
throw new IOException(
|
|
||||||
"Command line threw an InterruptedException '" + ex.getMessage() +
|
|
||||||
"' for command " + Arrays.asList(cmdAttribs));
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(in);
|
|
||||||
IOUtils.closeQuietly(out);
|
|
||||||
IOUtils.closeQuietly(err);
|
|
||||||
IOUtils.closeQuietly(inr);
|
|
||||||
if (proc != null) {
|
|
||||||
proc.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the process to the operating system.
|
|
||||||
*
|
|
||||||
* @param cmdAttribs the command line parameters
|
|
||||||
* @return the process
|
|
||||||
* @throws IOException if an error occurs
|
|
||||||
*/
|
|
||||||
Process openProcess(String[] cmdAttribs) throws IOException {
|
|
||||||
return Runtime.getRuntime().exec(cmdAttribs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dumps data in hexadecimal format.
|
|
||||||
* <p>
|
|
||||||
* Provides a single function to take an array of bytes and display it
|
|
||||||
* in hexadecimal form.
|
|
||||||
* <p>
|
|
||||||
* Origin of code: POI.
|
|
||||||
*
|
|
||||||
* @author Scott Sanders
|
|
||||||
* @author Marc Johnson
|
|
||||||
* @version $Id: HexDump.java 596667 2007-11-20 13:50:14Z niallp $
|
|
||||||
*/
|
|
||||||
public class HexDump {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instances should NOT be constructed in standard programming.
|
|
||||||
*/
|
|
||||||
public HexDump() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dump an array of bytes to an OutputStream.
|
|
||||||
*
|
|
||||||
* @param data the byte array to be dumped
|
|
||||||
* @param offset its offset, whatever that might mean
|
|
||||||
* @param stream the OutputStream to which the data is to be
|
|
||||||
* written
|
|
||||||
* @param index initial index into the byte array
|
|
||||||
*
|
|
||||||
* @throws IOException is thrown if anything goes wrong writing
|
|
||||||
* the data to stream
|
|
||||||
* @throws ArrayIndexOutOfBoundsException if the index is
|
|
||||||
* outside the data array's bounds
|
|
||||||
* @throws IllegalArgumentException if the output stream is null
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static void dump(byte[] data, long offset,
|
|
||||||
OutputStream stream, int index)
|
|
||||||
throws IOException, ArrayIndexOutOfBoundsException,
|
|
||||||
IllegalArgumentException {
|
|
||||||
|
|
||||||
if ((index < 0) || (index >= data.length)) {
|
|
||||||
throw new ArrayIndexOutOfBoundsException(
|
|
||||||
"illegal index: " + index + " into array of length "
|
|
||||||
+ data.length);
|
|
||||||
}
|
|
||||||
if (stream == null) {
|
|
||||||
throw new IllegalArgumentException("cannot write to nullstream");
|
|
||||||
}
|
|
||||||
long display_offset = offset + index;
|
|
||||||
StringBuffer buffer = new StringBuffer(74);
|
|
||||||
|
|
||||||
for (int j = index; j < data.length; j += 16) {
|
|
||||||
int chars_read = data.length - j;
|
|
||||||
|
|
||||||
if (chars_read > 16) {
|
|
||||||
chars_read = 16;
|
|
||||||
}
|
|
||||||
dump(buffer, display_offset).append(' ');
|
|
||||||
for (int k = 0; k < 16; k++) {
|
|
||||||
if (k < chars_read) {
|
|
||||||
dump(buffer, data[k + j]);
|
|
||||||
} else {
|
|
||||||
buffer.append(" ");
|
|
||||||
}
|
|
||||||
buffer.append(' ');
|
|
||||||
}
|
|
||||||
for (int k = 0; k < chars_read; k++) {
|
|
||||||
if ((data[k + j] >= ' ') && (data[k + j] < 127)) {
|
|
||||||
buffer.append((char) data[k + j]);
|
|
||||||
} else {
|
|
||||||
buffer.append('.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.append(EOL);
|
|
||||||
stream.write(buffer.toString().getBytes());
|
|
||||||
stream.flush();
|
|
||||||
buffer.setLength(0);
|
|
||||||
display_offset += chars_read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The line-separator (initializes to "line.separator" system property.
|
|
||||||
*/
|
|
||||||
public static final String EOL =
|
|
||||||
System.getProperty("line.separator");
|
|
||||||
private static final char[] _hexcodes =
|
|
||||||
{
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F'
|
|
||||||
};
|
|
||||||
private static final int[] _shifts =
|
|
||||||
{
|
|
||||||
28, 24, 20, 16, 12, 8, 4, 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dump a long value into a StringBuffer.
|
|
||||||
*
|
|
||||||
* @param _lbuffer the StringBuffer to dump the value in
|
|
||||||
* @param value the long value to be dumped
|
|
||||||
* @return StringBuffer containing the dumped value.
|
|
||||||
*/
|
|
||||||
private static StringBuffer dump(StringBuffer _lbuffer, long value) {
|
|
||||||
for (int j = 0; j < 8; j++) {
|
|
||||||
_lbuffer
|
|
||||||
.append(_hexcodes[((int) (value >> _shifts[j])) & 15]);
|
|
||||||
}
|
|
||||||
return _lbuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dump a byte value into a StringBuffer.
|
|
||||||
*
|
|
||||||
* @param _cbuffer the StringBuffer to dump the value in
|
|
||||||
* @param value the byte value to be dumped
|
|
||||||
* @return StringBuffer containing the dumped value.
|
|
||||||
*/
|
|
||||||
private static StringBuffer dump(StringBuffer _cbuffer, byte value) {
|
|
||||||
for (int j = 0; j < 2; j++) {
|
|
||||||
_cbuffer.append(_hexcodes[(value >> _shifts[j + 6]) & 15]);
|
|
||||||
}
|
|
||||||
return _cbuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,238 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enumeration of IO case sensitivity.
|
|
||||||
* <p>
|
|
||||||
* Different filing systems have different rules for case-sensitivity.
|
|
||||||
* Windows is case-insensitive, Unix is case-sensitive.
|
|
||||||
* <p>
|
|
||||||
* This class captures that difference, providing an enumeration to
|
|
||||||
* control how filename comparisons should be performed. It also provides
|
|
||||||
* methods that use the enumeration to perform comparisons.
|
|
||||||
* <p>
|
|
||||||
* Wherever possible, you should use the <code>check</code> methods in this
|
|
||||||
* class to compare filenames.
|
|
||||||
*
|
|
||||||
* @author Stephen Colebourne
|
|
||||||
* @version $Id: IOCase.java 606345 2007-12-21 23:43:01Z ggregory $
|
|
||||||
* @since Commons IO 1.3
|
|
||||||
*/
|
|
||||||
public final class IOCase implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The constant for case sensitive regardless of operating system.
|
|
||||||
*/
|
|
||||||
public static final IOCase SENSITIVE = new IOCase("Sensitive", true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The constant for case insensitive regardless of operating system.
|
|
||||||
*/
|
|
||||||
public static final IOCase INSENSITIVE = new IOCase("Insensitive", false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The constant for case sensitivity determined by the current operating system.
|
|
||||||
* Windows is case-insensitive when comparing filenames, Unix is case-sensitive.
|
|
||||||
* <p>
|
|
||||||
* If you derialize this constant of Windows, and deserialize on Unix, or vice
|
|
||||||
* versa, then the value of the case-sensitivity flag will change.
|
|
||||||
*/
|
|
||||||
public static final IOCase SYSTEM = new IOCase("System", !FilenameUtils.isSystemWindows());
|
|
||||||
|
|
||||||
/** Serialization version. */
|
|
||||||
private static final long serialVersionUID = -6343169151696340687L;
|
|
||||||
|
|
||||||
/** The enumeration name. */
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
/** The sensitivity flag. */
|
|
||||||
private final transient boolean sensitive;
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Factory method to create an IOCase from a name.
|
|
||||||
*
|
|
||||||
* @param name the name to find
|
|
||||||
* @return the IOCase object
|
|
||||||
* @throws IllegalArgumentException if the name is invalid
|
|
||||||
*/
|
|
||||||
public static IOCase forName(String name) {
|
|
||||||
if (IOCase.SENSITIVE.name.equals(name)){
|
|
||||||
return IOCase.SENSITIVE;
|
|
||||||
}
|
|
||||||
if (IOCase.INSENSITIVE.name.equals(name)){
|
|
||||||
return IOCase.INSENSITIVE;
|
|
||||||
}
|
|
||||||
if (IOCase.SYSTEM.name.equals(name)){
|
|
||||||
return IOCase.SYSTEM;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Invalid IOCase name: " + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Private constructor.
|
|
||||||
*
|
|
||||||
* @param name the name
|
|
||||||
* @param sensitive the sensitivity
|
|
||||||
*/
|
|
||||||
private IOCase(String name, boolean sensitive) {
|
|
||||||
this.name = name;
|
|
||||||
this.sensitive = sensitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the enumeration from the stream with a real one.
|
|
||||||
* This ensures that the correct flag is set for SYSTEM.
|
|
||||||
*
|
|
||||||
* @return the resolved object
|
|
||||||
*/
|
|
||||||
private Object readResolve() {
|
|
||||||
return forName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Gets the name of the constant.
|
|
||||||
*
|
|
||||||
* @return the name of the constant
|
|
||||||
*/
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the object represent case sensitive comparison.
|
|
||||||
*
|
|
||||||
* @return true if case sensitive
|
|
||||||
*/
|
|
||||||
public boolean isCaseSensitive() {
|
|
||||||
return sensitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Compares two strings using the case-sensitivity rule.
|
|
||||||
* <p>
|
|
||||||
* This method mimics {@link String#compareTo} but takes case-sensitivity
|
|
||||||
* into account.
|
|
||||||
*
|
|
||||||
* @param str1 the first string to compare, not null
|
|
||||||
* @param str2 the second string to compare, not null
|
|
||||||
* @return true if equal using the case rules
|
|
||||||
* @throws NullPointerException if either string is null
|
|
||||||
*/
|
|
||||||
public int checkCompareTo(String str1, String str2) {
|
|
||||||
if (str1 == null || str2 == null) {
|
|
||||||
throw new NullPointerException("The strings must not be null");
|
|
||||||
}
|
|
||||||
return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two strings using the case-sensitivity rule.
|
|
||||||
* <p>
|
|
||||||
* This method mimics {@link String#equals} but takes case-sensitivity
|
|
||||||
* into account.
|
|
||||||
*
|
|
||||||
* @param str1 the first string to compare, not null
|
|
||||||
* @param str2 the second string to compare, not null
|
|
||||||
* @return true if equal using the case rules
|
|
||||||
* @throws NullPointerException if either string is null
|
|
||||||
*/
|
|
||||||
public boolean checkEquals(String str1, String str2) {
|
|
||||||
if (str1 == null || str2 == null) {
|
|
||||||
throw new NullPointerException("The strings must not be null");
|
|
||||||
}
|
|
||||||
return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if one string starts with another using the case-sensitivity rule.
|
|
||||||
* <p>
|
|
||||||
* This method mimics {@link String#startsWith(String)} but takes case-sensitivity
|
|
||||||
* into account.
|
|
||||||
*
|
|
||||||
* @param str the string to check, not null
|
|
||||||
* @param start the start to compare against, not null
|
|
||||||
* @return true if equal using the case rules
|
|
||||||
* @throws NullPointerException if either string is null
|
|
||||||
*/
|
|
||||||
public boolean checkStartsWith(String str, String start) {
|
|
||||||
return str.regionMatches(!sensitive, 0, start, 0, start.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if one string ends with another using the case-sensitivity rule.
|
|
||||||
* <p>
|
|
||||||
* This method mimics {@link String#endsWith} but takes case-sensitivity
|
|
||||||
* into account.
|
|
||||||
*
|
|
||||||
* @param str the string to check, not null
|
|
||||||
* @param end the end to compare against, not null
|
|
||||||
* @return true if equal using the case rules
|
|
||||||
* @throws NullPointerException if either string is null
|
|
||||||
*/
|
|
||||||
public boolean checkEndsWith(String str, String end) {
|
|
||||||
int endLen = end.length();
|
|
||||||
return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if one string contains another at a specific index using the case-sensitivity rule.
|
|
||||||
* <p>
|
|
||||||
* This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)}
|
|
||||||
* but takes case-sensitivity into account.
|
|
||||||
*
|
|
||||||
* @param str the string to check, not null
|
|
||||||
* @param strStartIndex the index to start at in str
|
|
||||||
* @param search the start to search for, not null
|
|
||||||
* @return true if equal using the case rules
|
|
||||||
* @throws NullPointerException if either string is null
|
|
||||||
*/
|
|
||||||
public boolean checkRegionMatches(String str, int strStartIndex, String search) {
|
|
||||||
return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the case of the input String to a standard format.
|
|
||||||
* Subsequent operations can then use standard String methods.
|
|
||||||
*
|
|
||||||
* @param str the string to convert, null returns null
|
|
||||||
* @return the lower-case version if case-insensitive
|
|
||||||
*/
|
|
||||||
String convertCase(String str) {
|
|
||||||
if (str == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return sensitive ? str : str.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Gets a string describing the sensitivity.
|
|
||||||
*
|
|
||||||
* @return a string describing the sensitivity
|
|
||||||
*/
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclasses IOException with the {@link Throwable} constructors missing before Java 6. If you are using Java 6,
|
|
||||||
* consider this class deprecated and use {@link IOException}.
|
|
||||||
*
|
|
||||||
* @author <a href="http://commons.apache.org/io/">Apache Commons IO</a>
|
|
||||||
* @version $Id$
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
public class IOExceptionWithCause extends IOException {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the serial version UID.
|
|
||||||
*/
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance with the given message and cause.
|
|
||||||
* <p>
|
|
||||||
* As specified in {@link Throwable}, the message in the given <code>cause</code> is not used in this instance's
|
|
||||||
* message.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* the message (see {@link #getMessage()})
|
|
||||||
* @param cause
|
|
||||||
* the cause (see {@link #getCause()}). A <code>null</code> value is allowed.
|
|
||||||
*/
|
|
||||||
public IOExceptionWithCause(String message, Throwable cause) {
|
|
||||||
super(message);
|
|
||||||
this.initCause(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance with the given cause.
|
|
||||||
* <p>
|
|
||||||
* The message is set to <code>cause==null ? null : cause.toString()</code>, which by default contains the class
|
|
||||||
* and message of <code>cause</code>. This constructor is useful for call sites that just wrap another throwable.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param cause
|
|
||||||
* the cause (see {@link #getCause()}). A <code>null</code> value is allowed.
|
|
||||||
*/
|
|
||||||
public IOExceptionWithCause(Throwable cause) {
|
|
||||||
super(cause == null ? null : cause.toString());
|
|
||||||
this.initCause(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An Iterator over the lines in a <code>Reader</code>.
|
|
||||||
* <p>
|
|
||||||
* <code>LineIterator</code> holds a reference to an open <code>Reader</code>.
|
|
||||||
* When you have finished with the iterator you should close the reader
|
|
||||||
* to free internal resources. This can be done by closing the reader directly,
|
|
||||||
* or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)}
|
|
||||||
* method on the iterator.
|
|
||||||
* <p>
|
|
||||||
* The recommended usage pattern is:
|
|
||||||
* <pre>
|
|
||||||
* LineIterator it = FileUtils.lineIterator(file, "UTF-8");
|
|
||||||
* try {
|
|
||||||
* while (it.hasNext()) {
|
|
||||||
* String line = it.nextLine();
|
|
||||||
* /// do something with line
|
|
||||||
* }
|
|
||||||
* } finally {
|
|
||||||
* LineIterator.closeQuietly(iterator);
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @author Niall Pemberton
|
|
||||||
* @author Stephen Colebourne
|
|
||||||
* @author Sandy McArthur
|
|
||||||
* @version $Id: LineIterator.java 437567 2006-08-28 06:39:07Z bayard $
|
|
||||||
* @since Commons IO 1.2
|
|
||||||
*/
|
|
||||||
public class LineIterator implements Iterator {
|
|
||||||
|
|
||||||
/** The reader that is being read. */
|
|
||||||
private final BufferedReader bufferedReader;
|
|
||||||
/** The current line. */
|
|
||||||
private String cachedLine;
|
|
||||||
/** A flag indicating if the iterator has been fully read. */
|
|
||||||
private boolean finished = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an iterator of the lines for a <code>Reader</code>.
|
|
||||||
*
|
|
||||||
* @param reader the <code>Reader</code> to read from, not null
|
|
||||||
* @throws IllegalArgumentException if the reader is null
|
|
||||||
*/
|
|
||||||
public LineIterator(final Reader reader) throws IllegalArgumentException {
|
|
||||||
if (reader == null) {
|
|
||||||
throw new IllegalArgumentException("Reader must not be null");
|
|
||||||
}
|
|
||||||
if (reader instanceof BufferedReader) {
|
|
||||||
bufferedReader = (BufferedReader) reader;
|
|
||||||
} else {
|
|
||||||
bufferedReader = new BufferedReader(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Indicates whether the <code>Reader</code> has more lines.
|
|
||||||
* If there is an <code>IOException</code> then {@link #close()} will
|
|
||||||
* be called on this instance.
|
|
||||||
*
|
|
||||||
* @return <code>true</code> if the Reader has more lines
|
|
||||||
* @throws IllegalStateException if an IO exception occurs
|
|
||||||
*/
|
|
||||||
public boolean hasNext() {
|
|
||||||
if (cachedLine != null) {
|
|
||||||
return true;
|
|
||||||
} else if (finished) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
String line = bufferedReader.readLine();
|
|
||||||
if (line == null) {
|
|
||||||
finished = true;
|
|
||||||
return false;
|
|
||||||
} else if (isValidLine(line)) {
|
|
||||||
cachedLine = line;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(IOException ioe) {
|
|
||||||
close();
|
|
||||||
throw new IllegalStateException(ioe.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridable method to validate each line that is returned.
|
|
||||||
*
|
|
||||||
* @param line the line that is to be validated
|
|
||||||
* @return true if valid, false to remove from the iterator
|
|
||||||
*/
|
|
||||||
protected boolean isValidLine(String line) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next line in the wrapped <code>Reader</code>.
|
|
||||||
*
|
|
||||||
* @return the next line from the input
|
|
||||||
* @throws NoSuchElementException if there is no line to return
|
|
||||||
*/
|
|
||||||
public Object next() {
|
|
||||||
return nextLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next line in the wrapped <code>Reader</code>.
|
|
||||||
*
|
|
||||||
* @return the next line from the input
|
|
||||||
* @throws NoSuchElementException if there is no line to return
|
|
||||||
*/
|
|
||||||
public String nextLine() {
|
|
||||||
if (!hasNext()) {
|
|
||||||
throw new NoSuchElementException("No more lines");
|
|
||||||
}
|
|
||||||
String currentLine = cachedLine;
|
|
||||||
cachedLine = null;
|
|
||||||
return currentLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the underlying <code>Reader</code> quietly.
|
|
||||||
* This method is useful if you only want to process the first few
|
|
||||||
* lines of a larger file. If you do not close the iterator
|
|
||||||
* then the <code>Reader</code> remains open.
|
|
||||||
* This method can safely be called multiple times.
|
|
||||||
*/
|
|
||||||
public void close() {
|
|
||||||
finished = true;
|
|
||||||
IOUtils.closeQuietly(bufferedReader);
|
|
||||||
cachedLine = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsupported.
|
|
||||||
*
|
|
||||||
* @throws UnsupportedOperationException always
|
|
||||||
*/
|
|
||||||
public void remove() {
|
|
||||||
throw new UnsupportedOperationException("Remove unsupported on LineIterator");
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Closes the iterator, handling null and ignoring exceptions.
|
|
||||||
*
|
|
||||||
* @param iterator the iterator to close
|
|
||||||
*/
|
|
||||||
public static void closeQuietly(LineIterator iterator) {
|
|
||||||
if (iterator != null) {
|
|
||||||
iterator.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.comparator;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two files using the <b>default</b> {@link File#compareTo(File)} method.
|
|
||||||
* <p>
|
|
||||||
* This comparator can be used to sort lists or arrays of files
|
|
||||||
* by using the default file comparison.
|
|
||||||
* <p>
|
|
||||||
* Example of sorting a list of files using the
|
|
||||||
* {@link #DEFAULT_COMPARATOR} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* List<File> list = ...
|
|
||||||
* Collections.sort(list, DefaultFileComparator.DEFAULT_COMPARATOR);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Example of doing a <i>reverse</i> sort of an array of files using the
|
|
||||||
* {@link #DEFAULT_REVERSE} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* File[] array = ...
|
|
||||||
* Arrays.sort(array, DefaultFileComparator.DEFAULT_REVERSE);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* @version $Revision: 609243 $ $Date: 2008-01-06 00:30:42 +0000 (Sun, 06 Jan 2008) $
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
public class DefaultFileComparator implements Comparator, Serializable {
|
|
||||||
|
|
||||||
/** Singleton default comparator instance */
|
|
||||||
public static final Comparator DEFAULT_COMPARATOR = new DefaultFileComparator();
|
|
||||||
|
|
||||||
/** Singleton reverse default comparator instance */
|
|
||||||
public static final Comparator DEFAULT_REVERSE = new ReverseComparator(DEFAULT_COMPARATOR);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the two files using the {@link File#compareTo(File)} method.
|
|
||||||
*
|
|
||||||
* @param obj1 The first file to compare
|
|
||||||
* @param obj2 The second file to compare
|
|
||||||
* @return the result of calling file1's
|
|
||||||
* {@link File#compareTo(File)} with file2 as the parameter.
|
|
||||||
*/
|
|
||||||
public int compare(Object obj1, Object obj2) {
|
|
||||||
File file1 = (File)obj1;
|
|
||||||
File file2 = (File)obj2;
|
|
||||||
return file1.compareTo(file2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.comparator;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
|
||||||
import org.apache.commons.io.IOCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the file name <b>extensions</b> for order
|
|
||||||
* (see {@link FilenameUtils#getExtension(String)}).
|
|
||||||
* <p>
|
|
||||||
* This comparator can be used to sort lists or arrays of files
|
|
||||||
* by their file extension either in a case-sensitive, case-insensitive or
|
|
||||||
* system dependant case sensitive way. A number of singleton instances
|
|
||||||
* are provided for the various case sensitivity options (using {@link IOCase})
|
|
||||||
* and the reverse of those options.
|
|
||||||
* <p>
|
|
||||||
* Example of a <i>case-sensitive</i> file extension sort using the
|
|
||||||
* {@link #EXTENSION_COMPARATOR} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* List<File> list = ...
|
|
||||||
* Collections.sort(list, ExtensionFileComparator.EXTENSION_COMPARATOR);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Example of a <i>reverse case-insensitive</i> file extension sort using the
|
|
||||||
* {@link #EXTENSION_INSENSITIVE_REVERSE} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* File[] array = ...
|
|
||||||
* Arrays.sort(array, ExtensionFileComparator.EXTENSION_INSENSITIVE_REVERSE);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* @version $Revision: 609243 $ $Date: 2008-01-06 00:30:42 +0000 (Sun, 06 Jan 2008) $
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
public class ExtensionFileComparator implements Comparator, Serializable {
|
|
||||||
|
|
||||||
/** Case-sensitive extension comparator instance (see {@link IOCase#SENSITIVE}) */
|
|
||||||
public static final Comparator EXTENSION_COMPARATOR = new ExtensionFileComparator();
|
|
||||||
|
|
||||||
/** Reverse case-sensitive extension comparator instance (see {@link IOCase#SENSITIVE}) */
|
|
||||||
public static final Comparator EXTENSION_REVERSE = new ReverseComparator(EXTENSION_COMPARATOR);
|
|
||||||
|
|
||||||
/** Case-insensitive extension comparator instance (see {@link IOCase#INSENSITIVE}) */
|
|
||||||
public static final Comparator EXTENSION_INSENSITIVE_COMPARATOR = new ExtensionFileComparator(IOCase.INSENSITIVE);
|
|
||||||
|
|
||||||
/** Reverse case-insensitive extension comparator instance (see {@link IOCase#INSENSITIVE}) */
|
|
||||||
public static final Comparator EXTENSION_INSENSITIVE_REVERSE
|
|
||||||
= new ReverseComparator(EXTENSION_INSENSITIVE_COMPARATOR);
|
|
||||||
|
|
||||||
/** System sensitive extension comparator instance (see {@link IOCase#SYSTEM}) */
|
|
||||||
public static final Comparator EXTENSION_SYSTEM_COMPARATOR = new ExtensionFileComparator(IOCase.SYSTEM);
|
|
||||||
|
|
||||||
/** Reverse system sensitive path comparator instance (see {@link IOCase#SYSTEM}) */
|
|
||||||
public static final Comparator EXTENSION_SYSTEM_REVERSE = new ReverseComparator(EXTENSION_SYSTEM_COMPARATOR);
|
|
||||||
|
|
||||||
/** Whether the comparison is case sensitive. */
|
|
||||||
private final IOCase caseSensitivity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a case sensitive file extension comparator instance.
|
|
||||||
*/
|
|
||||||
public ExtensionFileComparator() {
|
|
||||||
this.caseSensitivity = IOCase.SENSITIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a file extension comparator instance with the specified case-sensitivity.
|
|
||||||
*
|
|
||||||
* @param caseSensitivity how to handle case sensitivity, null means case-sensitive
|
|
||||||
*/
|
|
||||||
public ExtensionFileComparator(IOCase caseSensitivity) {
|
|
||||||
this.caseSensitivity = caseSensitivity == null ? IOCase.SENSITIVE : caseSensitivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the extensions of two files the specified case sensitivity.
|
|
||||||
*
|
|
||||||
* @param obj1 The first file to compare
|
|
||||||
* @param obj2 The second file to compare
|
|
||||||
* @return a negative value if the first file's extension
|
|
||||||
* is less than the second, zero if the extensions are the
|
|
||||||
* same and a positive value if the first files extension
|
|
||||||
* is greater than the second file.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public int compare(Object obj1, Object obj2) {
|
|
||||||
File file1 = (File)obj1;
|
|
||||||
File file2 = (File)obj2;
|
|
||||||
String suffix1 = FilenameUtils.getExtension(file1.getName());
|
|
||||||
String suffix2 = FilenameUtils.getExtension(file2.getName());
|
|
||||||
return caseSensitivity.checkCompareTo(suffix1, suffix2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.comparator;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the <b>last modified date/time</b> of two files for order
|
|
||||||
* (see {@link File#lastModified()}).
|
|
||||||
* <p>
|
|
||||||
* This comparator can be used to sort lists or arrays of files
|
|
||||||
* by their last modified date/time.
|
|
||||||
* <p>
|
|
||||||
* Example of sorting a list of files using the
|
|
||||||
* {@link #LASTMODIFIED_COMPARATOR} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* List<File> list = ...
|
|
||||||
* Collections.sort(list, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Example of doing a <i>reverse</i> sort of an array of files using the
|
|
||||||
* {@link #LASTMODIFIED_REVERSE} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* File[] array = ...
|
|
||||||
* Arrays.sort(array, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* @version $Revision: 609243 $ $Date: 2008-01-06 00:30:42 +0000 (Sun, 06 Jan 2008) $
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
public class LastModifiedFileComparator implements Comparator, Serializable {
|
|
||||||
|
|
||||||
/** Last modified comparator instance */
|
|
||||||
public static final Comparator LASTMODIFIED_COMPARATOR = new LastModifiedFileComparator();
|
|
||||||
|
|
||||||
/** Reverse last modified comparator instance */
|
|
||||||
public static final Comparator LASTMODIFIED_REVERSE = new ReverseComparator(LASTMODIFIED_COMPARATOR);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the last the last modified date/time of two files.
|
|
||||||
*
|
|
||||||
* @param obj1 The first file to compare
|
|
||||||
* @param obj2 The second file to compare
|
|
||||||
* @return a negative value if the first file's lastmodified date/time
|
|
||||||
* is less than the second, zero if the lastmodified date/time are the
|
|
||||||
* same and a positive value if the first files lastmodified date/time
|
|
||||||
* is greater than the second file.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public int compare(Object obj1, Object obj2) {
|
|
||||||
File file1 = (File)obj1;
|
|
||||||
File file2 = (File)obj2;
|
|
||||||
long result = file1.lastModified() - file2.lastModified();
|
|
||||||
if (result < 0) {
|
|
||||||
return -1;
|
|
||||||
} else if (result > 0) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.comparator;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the <b>names</b> of two files for order (see {@link File#getName()}).
|
|
||||||
* <p>
|
|
||||||
* This comparator can be used to sort lists or arrays of files
|
|
||||||
* by their name either in a case-sensitive, case-insensitive or
|
|
||||||
* system dependant case sensitive way. A number of singleton instances
|
|
||||||
* are provided for the various case sensitivity options (using {@link IOCase})
|
|
||||||
* and the reverse of those options.
|
|
||||||
* <p>
|
|
||||||
* Example of a <i>case-sensitive</i> file name sort using the
|
|
||||||
* {@link #NAME_COMPARATOR} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* List<File> list = ...
|
|
||||||
* Collections.sort(list, NameFileComparator.NAME_COMPARATOR);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Example of a <i>reverse case-insensitive</i> file name sort using the
|
|
||||||
* {@link #NAME_INSENSITIVE_REVERSE} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* File[] array = ...
|
|
||||||
* Arrays.sort(array, NameFileComparator.NAME_INSENSITIVE_REVERSE);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* @version $Revision: 609243 $ $Date: 2008-01-06 00:30:42 +0000 (Sun, 06 Jan 2008) $
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
public class NameFileComparator implements Comparator, Serializable {
|
|
||||||
|
|
||||||
/** Case-sensitive name comparator instance (see {@link IOCase#SENSITIVE}) */
|
|
||||||
public static final Comparator NAME_COMPARATOR = new NameFileComparator();
|
|
||||||
|
|
||||||
/** Reverse case-sensitive name comparator instance (see {@link IOCase#SENSITIVE}) */
|
|
||||||
public static final Comparator NAME_REVERSE = new ReverseComparator(NAME_COMPARATOR);
|
|
||||||
|
|
||||||
/** Case-insensitive name comparator instance (see {@link IOCase#INSENSITIVE}) */
|
|
||||||
public static final Comparator NAME_INSENSITIVE_COMPARATOR = new NameFileComparator(IOCase.INSENSITIVE);
|
|
||||||
|
|
||||||
/** Reverse case-insensitive name comparator instance (see {@link IOCase#INSENSITIVE}) */
|
|
||||||
public static final Comparator NAME_INSENSITIVE_REVERSE = new ReverseComparator(NAME_INSENSITIVE_COMPARATOR);
|
|
||||||
|
|
||||||
/** System sensitive name comparator instance (see {@link IOCase#SYSTEM}) */
|
|
||||||
public static final Comparator NAME_SYSTEM_COMPARATOR = new NameFileComparator(IOCase.SYSTEM);
|
|
||||||
|
|
||||||
/** Reverse system sensitive name comparator instance (see {@link IOCase#SYSTEM}) */
|
|
||||||
public static final Comparator NAME_SYSTEM_REVERSE = new ReverseComparator(NAME_SYSTEM_COMPARATOR);
|
|
||||||
|
|
||||||
/** Whether the comparison is case sensitive. */
|
|
||||||
private final IOCase caseSensitivity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a case sensitive file name comparator instance.
|
|
||||||
*/
|
|
||||||
public NameFileComparator() {
|
|
||||||
this.caseSensitivity = IOCase.SENSITIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a file name comparator instance with the specified case-sensitivity.
|
|
||||||
*
|
|
||||||
* @param caseSensitivity how to handle case sensitivity, null means case-sensitive
|
|
||||||
*/
|
|
||||||
public NameFileComparator(IOCase caseSensitivity) {
|
|
||||||
this.caseSensitivity = caseSensitivity == null ? IOCase.SENSITIVE : caseSensitivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the names of two files with the specified case sensitivity.
|
|
||||||
*
|
|
||||||
* @param obj1 The first file to compare
|
|
||||||
* @param obj2 The second file to compare
|
|
||||||
* @return a negative value if the first file's name
|
|
||||||
* is less than the second, zero if the names are the
|
|
||||||
* same and a positive value if the first files name
|
|
||||||
* is greater than the second file.
|
|
||||||
*/
|
|
||||||
public int compare(Object obj1, Object obj2) {
|
|
||||||
File file1 = (File)obj1;
|
|
||||||
File file2 = (File)obj2;
|
|
||||||
return caseSensitivity.checkCompareTo(file1.getName(), file2.getName());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.comparator;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the <b>path</b> of two files for order (see {@link File#getPath()}).
|
|
||||||
* <p>
|
|
||||||
* This comparator can be used to sort lists or arrays of files
|
|
||||||
* by their path either in a case-sensitive, case-insensitive or
|
|
||||||
* system dependant case sensitive way. A number of singleton instances
|
|
||||||
* are provided for the various case sensitivity options (using {@link IOCase})
|
|
||||||
* and the reverse of those options.
|
|
||||||
* <p>
|
|
||||||
* Example of a <i>case-sensitive</i> file path sort using the
|
|
||||||
* {@link #PATH_COMPARATOR} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* List<File> list = ...
|
|
||||||
* Collections.sort(list, PathFileComparator.PATH_COMPARATOR);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Example of a <i>reverse case-insensitive</i> file path sort using the
|
|
||||||
* {@link #PATH_INSENSITIVE_REVERSE} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* File[] array = ...
|
|
||||||
* Arrays.sort(array, PathFileComparator.PATH_INSENSITIVE_REVERSE);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* @version $Revision: 609243 $ $Date: 2008-01-06 00:30:42 +0000 (Sun, 06 Jan 2008) $
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
public class PathFileComparator implements Comparator, Serializable {
|
|
||||||
|
|
||||||
/** Case-sensitive path comparator instance (see {@link IOCase#SENSITIVE}) */
|
|
||||||
public static final Comparator PATH_COMPARATOR = new PathFileComparator();
|
|
||||||
|
|
||||||
/** Reverse case-sensitive path comparator instance (see {@link IOCase#SENSITIVE}) */
|
|
||||||
public static final Comparator PATH_REVERSE = new ReverseComparator(PATH_COMPARATOR);
|
|
||||||
|
|
||||||
/** Case-insensitive path comparator instance (see {@link IOCase#INSENSITIVE}) */
|
|
||||||
public static final Comparator PATH_INSENSITIVE_COMPARATOR = new PathFileComparator(IOCase.INSENSITIVE);
|
|
||||||
|
|
||||||
/** Reverse case-insensitive path comparator instance (see {@link IOCase#INSENSITIVE}) */
|
|
||||||
public static final Comparator PATH_INSENSITIVE_REVERSE = new ReverseComparator(PATH_INSENSITIVE_COMPARATOR);
|
|
||||||
|
|
||||||
/** System sensitive path comparator instance (see {@link IOCase#SYSTEM}) */
|
|
||||||
public static final Comparator PATH_SYSTEM_COMPARATOR = new PathFileComparator(IOCase.SYSTEM);
|
|
||||||
|
|
||||||
/** Reverse system sensitive path comparator instance (see {@link IOCase#SYSTEM}) */
|
|
||||||
public static final Comparator PATH_SYSTEM_REVERSE = new ReverseComparator(PATH_SYSTEM_COMPARATOR);
|
|
||||||
|
|
||||||
/** Whether the comparison is case sensitive. */
|
|
||||||
private final IOCase caseSensitivity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a case sensitive file path comparator instance.
|
|
||||||
*/
|
|
||||||
public PathFileComparator() {
|
|
||||||
this.caseSensitivity = IOCase.SENSITIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a file path comparator instance with the specified case-sensitivity.
|
|
||||||
*
|
|
||||||
* @param caseSensitivity how to handle case sensitivity, null means case-sensitive
|
|
||||||
*/
|
|
||||||
public PathFileComparator(IOCase caseSensitivity) {
|
|
||||||
this.caseSensitivity = caseSensitivity == null ? IOCase.SENSITIVE : caseSensitivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the paths of two files the specified case sensitivity.
|
|
||||||
*
|
|
||||||
* @param obj1 The first file to compare
|
|
||||||
* @param obj2 The second file to compare
|
|
||||||
* @return a negative value if the first file's path
|
|
||||||
* is less than the second, zero if the paths are the
|
|
||||||
* same and a positive value if the first files path
|
|
||||||
* is greater than the second file.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public int compare(Object obj1, Object obj2) {
|
|
||||||
File file1 = (File)obj1;
|
|
||||||
File file2 = (File)obj2;
|
|
||||||
return caseSensitivity.checkCompareTo(file1.getPath(), file2.getPath());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.comparator;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverses the result of comparing two objects using
|
|
||||||
* the delegate {@link Comparator}.
|
|
||||||
*
|
|
||||||
* @version $Revision: 609243 $ $Date: 2008-01-06 00:30:42 +0000 (Sun, 06 Jan 2008) $
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
class ReverseComparator implements Comparator, Serializable {
|
|
||||||
|
|
||||||
private final Comparator delegate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an instance with the sepecified delegate {@link Comparator}.
|
|
||||||
*
|
|
||||||
* @param delegate The comparator to delegate to
|
|
||||||
*/
|
|
||||||
public ReverseComparator(Comparator delegate) {
|
|
||||||
if (delegate == null) {
|
|
||||||
throw new IllegalArgumentException("Delegate comparator is missing");
|
|
||||||
}
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare using the delegate Comparator, but reversing the result.
|
|
||||||
*
|
|
||||||
* @param obj1 The first object to compare
|
|
||||||
* @param obj2 The second object to compare
|
|
||||||
* @return the result from the delegate {@link Comparator#compare(Object, Object)}
|
|
||||||
* reversing the value (i.e. positive becomes negative and vice versa)
|
|
||||||
*/
|
|
||||||
public int compare(Object obj1, Object obj2) {
|
|
||||||
return delegate.compare(obj2, obj1); // parameters switched round
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.comparator;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the <b>length/size</b> of two files for order (see
|
|
||||||
* {@link File#length()} and {@link FileUtils#sizeOfDirectory(File)}).
|
|
||||||
* <p>
|
|
||||||
* This comparator can be used to sort lists or arrays of files
|
|
||||||
* by their length/size.
|
|
||||||
* <p>
|
|
||||||
* Example of sorting a list of files using the
|
|
||||||
* {@link #SIZE_COMPARATOR} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* List<File> list = ...
|
|
||||||
* Collections.sort(list, LengthFileComparator.LENGTH_COMPARATOR);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Example of doing a <i>reverse</i> sort of an array of files using the
|
|
||||||
* {@link #SIZE_REVERSE} singleton instance:
|
|
||||||
* <pre>
|
|
||||||
* File[] array = ...
|
|
||||||
* Arrays.sort(array, LengthFileComparator.LENGTH_REVERSE);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* <strong>N.B.</strong> Directories are treated as <b>zero size</b> unless
|
|
||||||
* <code>sumDirectoryContents</code> is <code>true</code>.
|
|
||||||
*
|
|
||||||
* @version $Revision: 609243 $ $Date: 2008-01-06 00:30:42 +0000 (Sun, 06 Jan 2008) $
|
|
||||||
* @since Commons IO 1.4
|
|
||||||
*/
|
|
||||||
public class SizeFileComparator implements Comparator, Serializable {
|
|
||||||
|
|
||||||
/** Size comparator instance - directories are treated as zero size */
|
|
||||||
public static final Comparator SIZE_COMPARATOR = new SizeFileComparator();
|
|
||||||
|
|
||||||
/** Reverse size comparator instance - directories are treated as zero size */
|
|
||||||
public static final Comparator SIZE_REVERSE = new ReverseComparator(SIZE_COMPARATOR);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Size comparator instance which sums the size of a directory's contents
|
|
||||||
* using {@link FileUtils#sizeOfDirectory(File)}
|
|
||||||
*/
|
|
||||||
public static final Comparator SIZE_SUMDIR_COMPARATOR = new SizeFileComparator(true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse size comparator instance which sums the size of a directory's contents
|
|
||||||
* using {@link FileUtils#sizeOfDirectory(File)}
|
|
||||||
*/
|
|
||||||
public static final Comparator SIZE_SUMDIR_REVERSE = new ReverseComparator(SIZE_SUMDIR_COMPARATOR);
|
|
||||||
|
|
||||||
/** Whether the sum of the directory's contents should be calculated. */
|
|
||||||
private final boolean sumDirectoryContents;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a file size comparator instance (directories treated as zero size).
|
|
||||||
*/
|
|
||||||
public SizeFileComparator() {
|
|
||||||
this.sumDirectoryContents = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a file size comparator instance specifying whether the size of
|
|
||||||
* the directory contents should be aggregated.
|
|
||||||
* <p>
|
|
||||||
* If the <code>sumDirectoryContents</code> is <code>true</code> The size of
|
|
||||||
* directories is calculated using {@link FileUtils#sizeOfDirectory(File)}.
|
|
||||||
*
|
|
||||||
* @param sumDirectoryContents <code>true</code> if the sum of the directoryies contents
|
|
||||||
* should be calculated, otherwise <code>false</code> if directories should be treated
|
|
||||||
* as size zero (see {@link FileUtils#sizeOfDirectory(File)}).
|
|
||||||
*/
|
|
||||||
public SizeFileComparator(boolean sumDirectoryContents) {
|
|
||||||
this.sumDirectoryContents = sumDirectoryContents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the length of two files.
|
|
||||||
*
|
|
||||||
* @param obj1 The first file to compare
|
|
||||||
* @param obj2 The second file to compare
|
|
||||||
* @return a negative value if the first file's length
|
|
||||||
* is less than the second, zero if the lengths are the
|
|
||||||
* same and a positive value if the first files length
|
|
||||||
* is greater than the second file.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public int compare(Object obj1, Object obj2) {
|
|
||||||
File file1 = (File)obj1;
|
|
||||||
File file2 = (File)obj2;
|
|
||||||
long size1 = 0;
|
|
||||||
if (file1.isDirectory()) {
|
|
||||||
size1 = sumDirectoryContents && file1.exists() ? FileUtils.sizeOfDirectory(file1) : 0;
|
|
||||||
} else {
|
|
||||||
size1 = file1.length();
|
|
||||||
}
|
|
||||||
long size2 = 0;
|
|
||||||
if (file2.isDirectory()) {
|
|
||||||
size2 = sumDirectoryContents && file2.exists() ? FileUtils.sizeOfDirectory(file2) : 0;
|
|
||||||
} else {
|
|
||||||
size2 = file2.length();
|
|
||||||
}
|
|
||||||
long result = size1 - size2;
|
|
||||||
if (result < 0) {
|
|
||||||
return -1;
|
|
||||||
} else if (result > 0) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
|
||||||
<!--
|
|
||||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
contributor license agreements. See the NOTICE file distributed with
|
|
||||||
this work for additional information regarding copyright ownership.
|
|
||||||
The ASF licenses this file to You 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.
|
|
||||||
-->
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<p>This package provides various {@link java.util.Comparator} implementations
|
|
||||||
for {@link java.io.File}s.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.filefilter;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract class which implements the Java FileFilter and FilenameFilter
|
|
||||||
* interfaces via the IOFileFilter interface.
|
|
||||||
* <p>
|
|
||||||
* Note that a subclass <b>must</b> override one of the accept methods,
|
|
||||||
* otherwise your class will infinitely loop.
|
|
||||||
*
|
|
||||||
* @since Commons IO 1.0
|
|
||||||
* @version $Revision: 539231 $ $Date: 2007-05-18 04:10:33 +0100 (Fri, 18 May 2007) $
|
|
||||||
*
|
|
||||||
* @author Stephen Colebourne
|
|
||||||
*/
|
|
||||||
public abstract class AbstractFileFilter implements IOFileFilter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to see if the File should be accepted by this filter.
|
|
||||||
*
|
|
||||||
* @param file the File to check
|
|
||||||
* @return true if this file matches the test
|
|
||||||
*/
|
|
||||||
public boolean accept(File file) {
|
|
||||||
return accept(file.getParentFile(), file.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to see if the File should be accepted by this filter.
|
|
||||||
*
|
|
||||||
* @param dir the directory File to check
|
|
||||||
* @param name the filename within the directory to check
|
|
||||||
* @return true if this file matches the test
|
|
||||||
*/
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return accept(new File(dir, name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a String representaion of this file filter.
|
|
||||||
*
|
|
||||||
* @return a String representaion
|
|
||||||
*/
|
|
||||||
public String toString() {
|
|
||||||
String name = getClass().getName();
|
|
||||||
int period = name.lastIndexOf('.');
|
|
||||||
return (period > 0 ? name.substring(period + 1) : name);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.filefilter;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters files based on a cutoff time, can filter either newer
|
|
||||||
* files or files equal to or older.
|
|
||||||
* <p>
|
|
||||||
* For example, to print all files and directories in the
|
|
||||||
* current directory older than one day:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* File dir = new File(".");
|
|
||||||
* // We are interested in files older than one day
|
|
||||||
* long cutoff = System.currentTimeMillis() - (24 * 60 * 60 * 1000);
|
|
||||||
* String[] files = dir.list( new AgeFileFilter(cutoff) );
|
|
||||||
* for ( int i = 0; i < files.length; i++ ) {
|
|
||||||
* System.out.println(files[i]);
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @author Rahul Akolkar
|
|
||||||
* @version $Id: AgeFileFilter.java 606381 2007-12-22 02:03:16Z ggregory $
|
|
||||||
* @since Commons IO 1.2
|
|
||||||
*/
|
|
||||||
public class AgeFileFilter extends AbstractFileFilter implements Serializable {
|
|
||||||
|
|
||||||
/** The cutoff time threshold. */
|
|
||||||
private final long cutoff;
|
|
||||||
/** Whether the files accepted will be older or newer. */
|
|
||||||
private final boolean acceptOlder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new age file filter for files equal to or older than
|
|
||||||
* a certain cutoff
|
|
||||||
*
|
|
||||||
* @param cutoff the threshold age of the files
|
|
||||||
*/
|
|
||||||
public AgeFileFilter(long cutoff) {
|
|
||||||
this(cutoff, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new age file filter for files on any one side
|
|
||||||
* of a certain cutoff.
|
|
||||||
*
|
|
||||||
* @param cutoff the threshold age of the files
|
|
||||||
* @param acceptOlder if true, older files (at or before the cutoff)
|
|
||||||
* are accepted, else newer ones (after the cutoff).
|
|
||||||
*/
|
|
||||||
public AgeFileFilter(long cutoff, boolean acceptOlder) {
|
|
||||||
this.acceptOlder = acceptOlder;
|
|
||||||
this.cutoff = cutoff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new age file filter for files older than (at or before)
|
|
||||||
* a certain cutoff date.
|
|
||||||
*
|
|
||||||
* @param cutoffDate the threshold age of the files
|
|
||||||
*/
|
|
||||||
public AgeFileFilter(Date cutoffDate) {
|
|
||||||
this(cutoffDate, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new age file filter for files on any one side
|
|
||||||
* of a certain cutoff date.
|
|
||||||
*
|
|
||||||
* @param cutoffDate the threshold age of the files
|
|
||||||
* @param acceptOlder if true, older files (at or before the cutoff)
|
|
||||||
* are accepted, else newer ones (after the cutoff).
|
|
||||||
*/
|
|
||||||
public AgeFileFilter(Date cutoffDate, boolean acceptOlder) {
|
|
||||||
this(cutoffDate.getTime(), acceptOlder);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new age file filter for files older than (at or before)
|
|
||||||
* a certain File (whose last modification time will be used as reference).
|
|
||||||
*
|
|
||||||
* @param cutoffReference the file whose last modification
|
|
||||||
* time is usesd as the threshold age of the files
|
|
||||||
*/
|
|
||||||
public AgeFileFilter(File cutoffReference) {
|
|
||||||
this(cutoffReference, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new age file filter for files on any one side
|
|
||||||
* of a certain File (whose last modification time will be used as
|
|
||||||
* reference).
|
|
||||||
*
|
|
||||||
* @param cutoffReference the file whose last modification
|
|
||||||
* time is usesd as the threshold age of the files
|
|
||||||
* @param acceptOlder if true, older files (at or before the cutoff)
|
|
||||||
* are accepted, else newer ones (after the cutoff).
|
|
||||||
*/
|
|
||||||
public AgeFileFilter(File cutoffReference, boolean acceptOlder) {
|
|
||||||
this(cutoffReference.lastModified(), acceptOlder);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Checks to see if the last modification of the file matches cutoff
|
|
||||||
* favorably.
|
|
||||||
* <p>
|
|
||||||
* If last modification time equals cutoff and newer files are required,
|
|
||||||
* file <b>IS NOT</b> selected.
|
|
||||||
* If last modification time equals cutoff and older files are required,
|
|
||||||
* file <b>IS</b> selected.
|
|
||||||
*
|
|
||||||
* @param file the File to check
|
|
||||||
* @return true if the filename matches
|
|
||||||
*/
|
|
||||||
public boolean accept(File file) {
|
|
||||||
boolean newer = FileUtils.isFileNewer(file, cutoff);
|
|
||||||
return acceptOlder ? !newer : newer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a String representaion of this file filter.
|
|
||||||
*
|
|
||||||
* @return a String representaion
|
|
||||||
*/
|
|
||||||
public String toString() {
|
|
||||||
String condition = acceptOlder ? "<=" : ">";
|
|
||||||
return super.toString() + "(" + condition + cutoff + ")";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.filefilter;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link java.io.FileFilter} providing conditional AND logic across a list of
|
|
||||||
* file filters. This filter returns <code>true</code> if all filters in the
|
|
||||||
* list return <code>true</code>. Otherwise, it returns <code>false</code>.
|
|
||||||
* Checking of the file filter list stops when the first filter returns
|
|
||||||
* <code>false</code>.
|
|
||||||
*
|
|
||||||
* @since Commons IO 1.0
|
|
||||||
* @version $Revision: 606381 $ $Date: 2007-12-22 02:03:16 +0000 (Sat, 22 Dec 2007) $
|
|
||||||
*
|
|
||||||
* @author Steven Caswell
|
|
||||||
*/
|
|
||||||
public class AndFileFilter
|
|
||||||
extends AbstractFileFilter
|
|
||||||
implements ConditionalFileFilter, Serializable {
|
|
||||||
|
|
||||||
/** The list of file filters. */
|
|
||||||
private List fileFilters;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance of <code>AndFileFilter</code>.
|
|
||||||
*
|
|
||||||
* @since Commons IO 1.1
|
|
||||||
*/
|
|
||||||
public AndFileFilter() {
|
|
||||||
this.fileFilters = new ArrayList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance of <code>AndFileFilter</code>
|
|
||||||
* with the specified list of filters.
|
|
||||||
*
|
|
||||||
* @param fileFilters a List of IOFileFilter instances, copied, null ignored
|
|
||||||
* @since Commons IO 1.1
|
|
||||||
*/
|
|
||||||
public AndFileFilter(final List fileFilters) {
|
|
||||||
if (fileFilters == null) {
|
|
||||||
this.fileFilters = new ArrayList();
|
|
||||||
} else {
|
|
||||||
this.fileFilters = new ArrayList(fileFilters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new file filter that ANDs the result of two other filters.
|
|
||||||
*
|
|
||||||
* @param filter1 the first filter, must not be null
|
|
||||||
* @param filter2 the second filter, must not be null
|
|
||||||
* @throws IllegalArgumentException if either filter is null
|
|
||||||
*/
|
|
||||||
public AndFileFilter(IOFileFilter filter1, IOFileFilter filter2) {
|
|
||||||
if (filter1 == null || filter2 == null) {
|
|
||||||
throw new IllegalArgumentException("The filters must not be null");
|
|
||||||
}
|
|
||||||
this.fileFilters = new ArrayList();
|
|
||||||
addFileFilter(filter1);
|
|
||||||
addFileFilter(filter2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void addFileFilter(final IOFileFilter ioFileFilter) {
|
|
||||||
this.fileFilters.add(ioFileFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public List getFileFilters() {
|
|
||||||
return Collections.unmodifiableList(this.fileFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public boolean removeFileFilter(final IOFileFilter ioFileFilter) {
|
|
||||||
return this.fileFilters.remove(ioFileFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void setFileFilters(final List fileFilters) {
|
|
||||||
this.fileFilters = new ArrayList(fileFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public boolean accept(final File file) {
|
|
||||||
if (this.fileFilters.size() == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (Iterator iter = this.fileFilters.iterator(); iter.hasNext();) {
|
|
||||||
IOFileFilter fileFilter = (IOFileFilter) iter.next();
|
|
||||||
if (!fileFilter.accept(file)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public boolean accept(final File file, final String name) {
|
|
||||||
if (this.fileFilters.size() == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (Iterator iter = this.fileFilters.iterator(); iter.hasNext();) {
|
|
||||||
IOFileFilter fileFilter = (IOFileFilter) iter.next();
|
|
||||||
if (!fileFilter.accept(file, name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a String representaion of this file filter.
|
|
||||||
*
|
|
||||||
* @return a String representaion
|
|
||||||
*/
|
|
||||||
public String toString() {
|
|
||||||
StringBuffer buffer = new StringBuffer();
|
|
||||||
buffer.append(super.toString());
|
|
||||||
buffer.append("(");
|
|
||||||
if (fileFilters != null) {
|
|
||||||
for (int i = 0; i < fileFilters.size(); i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
buffer.append(",");
|
|
||||||
}
|
|
||||||
Object filter = fileFilters.get(i);
|
|
||||||
buffer.append(filter == null ? "null" : filter.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.append(")");
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You 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 org.apache.commons.io.filefilter;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This filter accepts <code>File</code>s that can be read.
|
|
||||||
* <p>
|
|
||||||
* Example, showing how to print out a list of the
|
|
||||||
* current directory's <i>readable</i> files:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* File dir = new File(".");
|
|
||||||
* String[] files = dir.list( CanReadFileFilter.CAN_READ );
|
|
||||||
* for ( int i = 0; i < files.length; i++ ) {
|
|
||||||
* System.out.println(files[i]);
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Example, showing how to print out a list of the
|
|
||||||
* current directory's <i>un-readable</i> files:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* File dir = new File(".");
|
|
||||||
* String[] files = dir.list( CanReadFileFilter.CANNOT_READ );
|
|
||||||
* for ( int i = 0; i < files.length; i++ ) {
|
|
||||||
* System.out.println(files[i]);
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Example, showing how to print out a list of the
|
|
||||||
* current directory's <i>read-only</i> files:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* File dir = new File(".");
|
|
||||||
* String[] files = dir.list( CanReadFileFilter.READ_ONLY );
|
|
||||||
* for ( int i = 0; i < files.length; i++ ) {
|
|
||||||
* System.out.println(files[i]);
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @since Commons IO 1.3
|
|
||||||
* @version $Revision: 587916 $
|
|
||||||
*/
|
|
||||||
public class CanReadFileFilter extends AbstractFileFilter implements Serializable {
|
|
||||||
|
|
||||||
/** Singleton instance of <i>readable</i> filter */
|
|
||||||
public static final IOFileFilter CAN_READ = new CanReadFileFilter();
|
|
||||||
|
|
||||||
/** Singleton instance of not <i>readable</i> filter */
|
|
||||||
public static final IOFileFilter CANNOT_READ = new NotFileFilter(CAN_READ);
|
|
||||||
|
|
||||||
/** Singleton instance of <i>read-only</i> filter */
|
|
||||||
public static final IOFileFilter READ_ONLY = new AndFileFilter(CAN_READ,
|
|
||||||
CanWriteFileFilter.CANNOT_WRITE);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restrictive consructor.
|
|
||||||
*/
|
|
||||||
protected CanReadFileFilter() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to see if the file can be read.
|
|
||||||
*
|
|
||||||
* @param file the File to check.
|
|
||||||
* @return <code>true</code> if the file can be
|
|
||||||
* read, otherwise <code>false</code>.
|
|
||||||
*/
|
|
||||||
public boolean accept(File file) {
|
|
||||||
return file.canRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user