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