2008-11-01 17:32:06 -04:00
2009-12-14 21:50:53 -05:00
package com.fsck.k9.mail.store ;
2008-11-01 17:32:06 -04:00
2012-03-05 15:04:34 -05:00
import java.io.ByteArrayInputStream ;
import java.io.File ;
import java.io.FileNotFoundException ;
import java.io.FileOutputStream ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.OutputStream ;
import java.io.Serializable ;
import java.io.UnsupportedEncodingException ;
2010-11-13 16:40:56 -05:00
import java.net.URLEncoder ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Date ;
import java.util.HashMap ;
2012-11-15 15:05:45 -05:00
import java.util.HashSet ;
2010-11-13 16:40:56 -05:00
import java.util.LinkedList ;
import java.util.List ;
2011-04-20 17:58:10 -04:00
import java.util.Locale ;
2010-11-13 16:40:56 -05:00
import java.util.Map ;
import java.util.Set ;
import java.util.UUID ;
2011-04-10 12:29:25 -04:00
import java.util.regex.Pattern ;
2010-11-13 16:40:56 -05:00
import org.apache.commons.io.IOUtils ;
2008-11-01 17:32:06 -04:00
import android.app.Application ;
2012-10-23 18:08:44 -04:00
import android.content.ContentResolver ;
2008-11-01 17:32:06 -04:00
import android.content.ContentValues ;
2010-11-13 16:40:56 -05:00
import android.content.Context ;
2009-12-09 22:16:42 -05:00
import android.content.SharedPreferences ;
2008-11-01 17:32:06 -04:00
import android.database.Cursor ;
import android.database.sqlite.SQLiteDatabase ;
2010-01-24 15:41:04 -05:00
import android.database.sqlite.SQLiteException ;
2008-11-01 17:32:06 -04:00
import android.net.Uri ;
2009-12-09 22:16:42 -05:00
import android.util.Log ;
2010-11-13 16:40:56 -05:00
2010-03-03 23:00:30 -05:00
import com.fsck.k9.Account ;
2009-12-14 21:50:53 -05:00
import com.fsck.k9.K9 ;
import com.fsck.k9.Preferences ;
2011-06-17 00:17:01 -04:00
import com.fsck.k9.R ;
2012-04-08 12:29:08 -04:00
import com.fsck.k9.Account.MessageFormat ;
2012-03-05 15:04:34 -05:00
import com.fsck.k9.activity.Search ;
2010-05-19 14:17:06 -04:00
import com.fsck.k9.controller.MessageRemovalListener ;
import com.fsck.k9.controller.MessageRetrievalListener ;
2012-03-05 15:04:34 -05:00
import com.fsck.k9.helper.HtmlConverter ;
2012-10-28 21:27:34 -04:00
import com.fsck.k9.helper.StringUtils ;
2010-05-19 14:17:06 -04:00
import com.fsck.k9.helper.Utility ;
2010-11-13 16:40:56 -05:00
import com.fsck.k9.mail.Address ;
import com.fsck.k9.mail.Body ;
import com.fsck.k9.mail.BodyPart ;
import com.fsck.k9.mail.FetchProfile ;
import com.fsck.k9.mail.Flag ;
import com.fsck.k9.mail.Folder ;
import com.fsck.k9.mail.Message ;
2010-12-18 17:56:40 -05:00
import com.fsck.k9.mail.Message.RecipientType ;
2010-11-13 16:40:56 -05:00
import com.fsck.k9.mail.MessagingException ;
import com.fsck.k9.mail.Part ;
import com.fsck.k9.mail.Store ;
2010-05-19 14:17:06 -04:00
import com.fsck.k9.mail.filter.Base64OutputStream ;
2010-11-13 16:40:56 -05:00
import com.fsck.k9.mail.internet.MimeBodyPart ;
import com.fsck.k9.mail.internet.MimeHeader ;
import com.fsck.k9.mail.internet.MimeMessage ;
import com.fsck.k9.mail.internet.MimeMultipart ;
import com.fsck.k9.mail.internet.MimeUtility ;
2012-02-13 17:11:59 -05:00
import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer ;
2010-11-13 16:40:56 -05:00
import com.fsck.k9.mail.internet.TextBody ;
2010-12-18 17:56:40 -05:00
import com.fsck.k9.mail.store.LockableDatabase.DbCallback ;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException ;
2010-11-13 16:40:56 -05:00
import com.fsck.k9.mail.store.StorageManager.StorageProvider ;
2009-12-14 21:50:53 -05:00
import com.fsck.k9.provider.AttachmentProvider ;
2012-10-23 18:08:44 -04:00
import com.fsck.k9.provider.EmailProvider ;
2013-02-18 22:45:14 -05:00
import com.fsck.k9.provider.EmailProvider.MessageColumns ;
2012-10-13 08:53:00 -04:00
import com.fsck.k9.search.LocalSearch ;
2012-11-02 20:52:45 -04:00
import com.fsck.k9.search.SearchSpecification.Attribute ;
import com.fsck.k9.search.SearchSpecification.Searchfield ;
2012-10-28 21:27:34 -04:00
import com.fsck.k9.search.SqlQueryBuilder ;
2008-11-01 17:32:06 -04:00
/ * *
* < pre >
* Implements a SQLite database backed local store for Messages .
* < / pre >
* /
2011-02-06 17:09:48 -05:00
public class LocalStore extends Store implements Serializable {
2010-08-02 07:55:31 -04:00
2011-01-31 18:45:14 -05:00
private static final long serialVersionUID = - 5142141896809423072L ;
2010-08-07 11:10:07 -04:00
private static final Message [ ] EMPTY_MESSAGE_ARRAY = new Message [ 0 ] ;
2010-08-02 07:55:31 -04:00
private static final String [ ] EMPTY_STRING_ARRAY = new String [ 0 ] ;
2012-10-08 16:51:29 -04:00
private static final Flag [ ] EMPTY_FLAG_ARRAY = new Flag [ 0 ] ;
2010-08-02 07:55:31 -04:00
2009-12-27 11:53:31 -05:00
/ *
* a String containing the columns getMessages expects to work with
* in the correct order .
* /
static private String GET_MESSAGES_COLS =
2013-01-10 21:40:35 -05:00
" subject, sender_list, date, uid, flags, messages.id, to_list, cc_list, " +
" bcc_list, reply_to_list, attachment_count, internal_date, messages.message_id, " +
" folder_id, preview, threads.id, threads.root, deleted, read, flagged, answered, " +
" forwarded " ;
2009-12-27 11:53:31 -05:00
2012-12-07 09:04:53 -05:00
static private String GET_FOLDER_COLS = " folders.id, name, SUM(read=0), visible_limit, last_updated, status, push_state, last_pushed, SUM(flagged), integrate, top_group, poll_class, push_class, display_class " ;
2011-01-23 22:27:19 -05:00
2012-11-15 15:05:45 -05:00
private static final String [ ] UID_CHECK_PROJECTION = { " uid " } ;
/ * *
2013-03-07 19:15:26 -05:00
* Maximum number of UIDs to check for existence at once .
2012-11-15 15:05:45 -05:00
*
* @see LocalFolder # extractNewMessages ( List )
* /
private static final int UID_CHECK_BATCH_SIZE = 500 ;
2011-01-23 22:27:19 -05:00
2012-12-05 23:25:41 -05:00
/ * *
2013-03-07 19:15:26 -05:00
* Maximum number of messages to perform flag updates on at once .
2012-12-05 23:25:41 -05:00
*
* @see # setFlag ( List , Flag , boolean , boolean )
* /
private static final int FLAG_UPDATE_BATCH_SIZE = 500 ;
2013-01-11 20:28:12 -05:00
/ * *
2013-03-07 19:15:26 -05:00
* Maximum number of threads to perform flag updates on at once .
2013-01-11 20:28:12 -05:00
*
* @see # setFlagForThreads ( List , Flag , boolean )
* /
2013-03-07 19:15:26 -05:00
private static final int THREAD_FLAG_UPDATE_BATCH_SIZE = 500 ;
2013-01-11 20:28:12 -05:00
2013-03-07 19:15:26 -05:00
public static final int DB_VERSION = 48 ;
2010-11-13 16:40:56 -05:00
2013-02-18 22:45:14 -05:00
public static String getColumnNameForFlag ( Flag flag ) {
switch ( flag ) {
case SEEN : {
return MessageColumns . READ ;
}
case FLAGGED : {
return MessageColumns . FLAGGED ;
}
case ANSWERED : {
return MessageColumns . ANSWERED ;
}
case FORWARDED : {
return MessageColumns . FORWARDED ;
}
default : {
throw new IllegalArgumentException ( " Flag must be a special column flag " ) ;
}
}
}
2010-12-18 17:56:40 -05:00
protected String uUid = null ;
private final Application mApplication ;
private LockableDatabase database ;
2010-11-13 16:40:56 -05:00
2012-10-23 18:08:44 -04:00
private ContentResolver mContentResolver ;
2008-11-01 17:32:06 -04:00
/ * *
2010-03-03 23:00:30 -05:00
* local : //localhost/path/to/database/uuid.db
2010-11-13 16:40:56 -05:00
* This constructor is only used by { @link Store # getLocalInstance ( Account , Application ) }
2010-12-28 04:10:50 -05:00
* @param account
* @param application
2010-11-13 16:40:56 -05:00
* @throws UnavailableStorageException if not { @link StorageProvider # isReady ( Context ) }
2008-11-01 17:32:06 -04:00
* /
2011-02-06 17:09:48 -05:00
public LocalStore ( final Account account , final Application application ) throws MessagingException {
2010-03-03 23:00:30 -05:00
super ( account ) ;
2010-12-18 17:56:40 -05:00
database = new LockableDatabase ( application , account . getUuid ( ) , new StoreSchemaDefinition ( ) ) ;
2008-11-01 17:32:06 -04:00
mApplication = application ;
2012-10-23 18:08:44 -04:00
mContentResolver = application . getContentResolver ( ) ;
2010-12-18 17:56:40 -05:00
database . setStorageProviderId ( account . getLocalStorageProviderId ( ) ) ;
2010-11-13 16:40:56 -05:00
uUid = account . getUuid ( ) ;
2010-12-18 17:56:40 -05:00
database . open ( ) ;
2010-11-13 16:40:56 -05:00
}
2009-11-17 11:54:50 -05:00
2011-02-06 17:09:48 -05:00
public void switchLocalStorage ( final String newStorageProviderId ) throws MessagingException {
2010-12-18 17:56:40 -05:00
database . switchProvider ( newStorageProviderId ) ;
2010-11-13 16:40:56 -05:00
}
2010-01-24 15:41:04 -05:00
2011-02-06 17:09:48 -05:00
protected SharedPreferences getPreferences ( ) {
2011-02-04 07:26:14 -05:00
return Preferences . getPreferences ( mApplication ) . getPreferences ( ) ;
}
2011-02-06 17:09:48 -05:00
private class StoreSchemaDefinition implements LockableDatabase . SchemaDefinition {
2010-12-18 17:56:40 -05:00
@Override
2011-02-06 17:09:48 -05:00
public int getVersion ( ) {
2010-12-18 17:56:40 -05:00
return DB_VERSION ;
2010-11-13 16:40:56 -05:00
}
2010-12-24 13:55:05 -05:00
2010-12-18 17:56:40 -05:00
@Override
2011-02-06 17:09:48 -05:00
public void doDbUpgrade ( final SQLiteDatabase db ) {
2010-12-24 13:55:05 -05:00
Log . i ( K9 . LOG_TAG , String . format ( " Upgrading database from version %d to version %d " ,
db . getVersion ( ) , DB_VERSION ) ) ;
2009-11-29 23:03:16 -05:00
2010-01-24 15:41:04 -05:00
2010-12-24 13:55:05 -05:00
AttachmentProvider . clear ( mApplication ) ;
2010-11-13 16:40:56 -05:00
2012-05-30 06:45:37 -04:00
db . beginTransaction ( ) ;
try {
2012-07-07 11:14:03 -04:00
try {
// schema version 29 was when we moved to incremental updates
// in the case of a new db or a < v29 db, we blow away and start from scratch
if ( db . getVersion ( ) < 29 ) {
db . execSQL ( " DROP TABLE IF EXISTS folders " ) ;
db . execSQL ( " CREATE TABLE folders (id INTEGER PRIMARY KEY, name TEXT, "
+ " last_updated INTEGER, unread_count INTEGER, visible_limit INTEGER, status TEXT, "
+ " push_state TEXT, last_pushed INTEGER, flagged_count INTEGER default 0, "
+ " integrate INTEGER, top_group INTEGER, poll_class TEXT, push_class TEXT, display_class TEXT "
+ " ) " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS folder_name ON folders (name) " ) ;
db . execSQL ( " DROP TABLE IF EXISTS messages " ) ;
2012-10-08 16:51:29 -04:00
db . execSQL ( " CREATE TABLE messages ( " +
" id INTEGER PRIMARY KEY, " +
" deleted INTEGER default 0, " +
" folder_id INTEGER, " +
" uid TEXT, " +
" subject TEXT, " +
" date INTEGER, " +
" flags TEXT, " +
" sender_list TEXT, " +
" to_list TEXT, " +
" cc_list TEXT, " +
" bcc_list TEXT, " +
" reply_to_list TEXT, " +
" html_content TEXT, " +
" text_content TEXT, " +
" attachment_count INTEGER, " +
" internal_date INTEGER, " +
" message_id TEXT, " +
" preview TEXT, " +
" mime_type TEXT, " +
" normalized_subject_hash INTEGER, " +
2012-12-03 23:13:58 -05:00
" empty INTEGER, " +
" read INTEGER default 0, " +
" flagged INTEGER default 0, " +
" answered INTEGER default 0, " +
" forwarded INTEGER default 0 " +
2012-10-08 16:51:29 -04:00
" ) " ) ;
2012-07-07 11:14:03 -04:00
db . execSQL ( " DROP TABLE IF EXISTS headers " ) ;
db . execSQL ( " CREATE TABLE headers (id INTEGER PRIMARY KEY, message_id INTEGER, name TEXT, value TEXT) " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS header_folder ON headers (message_id) " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_uid ON messages (uid, folder_id) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS msg_folder_id " ) ;
2010-12-24 13:55:05 -05:00
db . execSQL ( " DROP INDEX IF EXISTS msg_folder_id_date " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date) " ) ;
2012-10-08 16:51:29 -04:00
2012-10-23 19:03:59 -04:00
db . execSQL ( " DROP INDEX IF EXISTS msg_empty " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty) " ) ;
2012-12-03 23:13:58 -05:00
db . execSQL ( " DROP INDEX IF EXISTS msg_read " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_read ON messages (read) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS msg_flagged " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged) " ) ;
2013-01-10 21:40:35 -05:00
db . execSQL ( " DROP TABLE IF EXISTS threads " ) ;
db . execSQL ( " CREATE TABLE threads ( " +
" id INTEGER PRIMARY KEY, " +
" message_id INTEGER, " +
" root INTEGER, " +
" parent INTEGER " +
" ) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS threads_message_id " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS threads_root " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS threads_root ON threads (root) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS threads_parent " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent) " ) ;
2013-03-07 19:15:26 -05:00
db . execSQL ( " DROP TRIGGER IF EXISTS set_thread_root " ) ;
db . execSQL ( " CREATE TRIGGER set_thread_root " +
" AFTER INSERT ON threads " +
" BEGIN " +
" UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
" END " ) ;
2013-01-10 21:40:35 -05:00
2012-07-07 11:14:03 -04:00
db . execSQL ( " DROP TABLE IF EXISTS attachments " ) ;
db . execSQL ( " CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER, "
+ " store_data TEXT, content_uri TEXT, size INTEGER, name TEXT, "
+ " mime_type TEXT, content_id TEXT, content_disposition TEXT) " ) ;
db . execSQL ( " DROP TABLE IF EXISTS pending_commands " ) ;
db . execSQL ( " CREATE TABLE pending_commands " +
" (id INTEGER PRIMARY KEY, command TEXT, arguments TEXT) " ) ;
db . execSQL ( " DROP TRIGGER IF EXISTS delete_folder " ) ;
db . execSQL ( " CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END; " ) ;
db . execSQL ( " DROP TRIGGER IF EXISTS delete_message " ) ;
db . execSQL ( " CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; "
+ " DELETE FROM headers where old.id = message_id; END; " ) ;
} else {
// in the case that we're starting out at 29 or newer, run all the needed updates
if ( db . getVersion ( ) < 30 ) {
try {
db . execSQL ( " ALTER TABLE messages ADD deleted INTEGER default 0 " ) ;
} catch ( SQLiteException e ) {
if ( ! e . toString ( ) . startsWith ( " duplicate column name: deleted " ) ) {
throw e ;
}
2010-12-24 13:55:05 -05:00
}
2010-01-24 15:41:04 -05:00
}
2012-07-07 11:14:03 -04:00
if ( db . getVersion ( ) < 31 ) {
db . execSQL ( " DROP INDEX IF EXISTS msg_folder_id_date " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date) " ) ;
}
if ( db . getVersion ( ) < 32 ) {
db . execSQL ( " UPDATE messages SET deleted = 1 WHERE flags LIKE '%DELETED%' " ) ;
}
if ( db . getVersion ( ) < 33 ) {
2010-01-24 15:41:04 -05:00
2012-07-07 11:14:03 -04:00
try {
db . execSQL ( " ALTER TABLE messages ADD preview TEXT " ) ;
} catch ( SQLiteException e ) {
if ( ! e . toString ( ) . startsWith ( " duplicate column name: preview " ) ) {
throw e ;
}
2010-12-24 13:55:05 -05:00
}
2012-07-07 11:14:03 -04:00
2010-04-16 10:33:54 -04:00
}
2012-07-07 11:14:03 -04:00
if ( db . getVersion ( ) < 34 ) {
try {
db . execSQL ( " ALTER TABLE folders ADD flagged_count INTEGER default 0 " ) ;
} catch ( SQLiteException e ) {
if ( ! e . getMessage ( ) . startsWith ( " duplicate column name: flagged_count " ) ) {
throw e ;
}
}
2010-12-24 13:55:05 -05:00
}
2012-07-07 11:14:03 -04:00
if ( db . getVersion ( ) < 35 ) {
try {
db . execSQL ( " update messages set flags = replace(flags, 'X_NO_SEEN_INFO', 'X_BAD_FLAG') " ) ;
} catch ( SQLiteException e ) {
Log . e ( K9 . LOG_TAG , " Unable to get rid of obsolete flag X_NO_SEEN_INFO " , e ) ;
}
2010-12-24 13:55:05 -05:00
}
2012-07-07 11:14:03 -04:00
if ( db . getVersion ( ) < 36 ) {
try {
db . execSQL ( " ALTER TABLE attachments ADD content_id TEXT " ) ;
} catch ( SQLiteException e ) {
Log . e ( K9 . LOG_TAG , " Unable to add content_id column to attachments " ) ;
}
2010-12-24 13:55:05 -05:00
}
2012-07-07 11:14:03 -04:00
if ( db . getVersion ( ) < 37 ) {
try {
db . execSQL ( " ALTER TABLE attachments ADD content_disposition TEXT " ) ;
} catch ( SQLiteException e ) {
Log . e ( K9 . LOG_TAG , " Unable to add content_disposition column to attachments " ) ;
}
2010-12-24 13:55:05 -05:00
}
2010-10-08 02:33:04 -04:00
2012-07-07 11:14:03 -04:00
// Database version 38 is solely to prune cached attachments now that we clear them better
if ( db . getVersion ( ) < 39 ) {
try {
db . execSQL ( " DELETE FROM headers WHERE id in (SELECT headers.id FROM headers LEFT JOIN messages ON headers.message_id = messages.id WHERE messages.id IS NULL) " ) ;
} catch ( SQLiteException e ) {
Log . e ( K9 . LOG_TAG , " Unable to remove extra header data from the database " ) ;
}
2011-01-12 18:48:28 -05:00
}
2010-10-08 02:33:04 -04:00
2012-07-07 11:14:03 -04:00
// V40: Store the MIME type for a message.
if ( db . getVersion ( ) < 40 ) {
try {
db . execSQL ( " ALTER TABLE messages ADD mime_type TEXT " ) ;
} catch ( SQLiteException e ) {
Log . e ( K9 . LOG_TAG , " Unable to add mime_type column to messages " ) ;
2011-01-15 23:23:03 -05:00
}
}
2012-07-07 11:14:03 -04:00
if ( db . getVersion ( ) < 41 ) {
try {
db . execSQL ( " ALTER TABLE folders ADD integrate INTEGER " ) ;
db . execSQL ( " ALTER TABLE folders ADD top_group INTEGER " ) ;
db . execSQL ( " ALTER TABLE folders ADD poll_class TEXT " ) ;
db . execSQL ( " ALTER TABLE folders ADD push_class TEXT " ) ;
db . execSQL ( " ALTER TABLE folders ADD display_class TEXT " ) ;
} catch ( SQLiteException e ) {
if ( ! e . getMessage ( ) . startsWith ( " duplicate column name: " ) ) {
throw e ;
}
}
Cursor cursor = null ;
try {
SharedPreferences prefs = getPreferences ( ) ;
cursor = db . rawQuery ( " SELECT id, name FROM folders " , null ) ;
while ( cursor . moveToNext ( ) ) {
try {
int id = cursor . getInt ( 0 ) ;
String name = cursor . getString ( 1 ) ;
update41Metadata ( db , prefs , id , name ) ;
} catch ( Exception e ) {
Log . e ( K9 . LOG_TAG , " error trying to ugpgrade a folder class " , e ) ;
}
2011-01-15 23:23:03 -05:00
}
}
2012-07-07 11:14:03 -04:00
catch ( SQLiteException e ) {
Log . e ( K9 . LOG_TAG , " Exception while upgrading database to v41. folder classes may have vanished " , e ) ;
2010-12-24 13:55:05 -05:00
2012-07-07 11:14:03 -04:00
} finally {
Utility . closeQuietly ( cursor ) ;
}
2011-01-15 23:23:03 -05:00
}
2012-07-07 11:14:03 -04:00
if ( db . getVersion ( ) = = 41 ) {
try {
long startTime = System . currentTimeMillis ( ) ;
SharedPreferences . Editor editor = getPreferences ( ) . edit ( ) ;
List < ? extends Folder > folders = getPersonalNamespaces ( true ) ;
for ( Folder folder : folders ) {
if ( folder instanceof LocalFolder ) {
LocalFolder lFolder = ( LocalFolder ) folder ;
lFolder . save ( editor ) ;
}
2011-02-04 07:26:14 -05:00
}
2012-07-07 11:14:03 -04:00
editor . commit ( ) ;
long endTime = System . currentTimeMillis ( ) ;
Log . i ( K9 . LOG_TAG , " Putting folder preferences for " + folders . size ( ) + " folders back into Preferences took " + ( endTime - startTime ) + " ms " ) ;
} catch ( Exception e ) {
Log . e ( K9 . LOG_TAG , " Could not replace Preferences in upgrade from DB_VERSION 41 " , e ) ;
2011-06-17 00:17:01 -04:00
}
2012-07-07 11:14:03 -04:00
}
if ( db . getVersion ( ) < 43 ) {
try {
// If folder "OUTBOX" (old, v3.800 - v3.802) exists, rename it to
// "K9MAIL_INTERNAL_OUTBOX" (new)
LocalFolder oldOutbox = new LocalFolder ( " OUTBOX " ) ;
if ( oldOutbox . exists ( ) ) {
ContentValues cv = new ContentValues ( ) ;
cv . put ( " name " , Account . OUTBOX ) ;
db . update ( " folders " , cv , " name = ? " , new String [ ] { " OUTBOX " } ) ;
Log . i ( K9 . LOG_TAG , " Renamed folder OUTBOX to " + Account . OUTBOX ) ;
2011-06-17 00:17:01 -04:00
}
2012-07-07 11:14:03 -04:00
// Check if old (pre v3.800) localized outbox folder exists
String localizedOutbox = K9 . app . getString ( R . string . special_mailbox_name_outbox ) ;
LocalFolder obsoleteOutbox = new LocalFolder ( localizedOutbox ) ;
if ( obsoleteOutbox . exists ( ) ) {
// Get all messages from the localized outbox ...
Message [ ] messages = obsoleteOutbox . getMessages ( null , false ) ;
if ( messages . length > 0 ) {
// ... and move them to the drafts folder (we don't want to
// surprise the user by sending potentially very old messages)
LocalFolder drafts = new LocalFolder ( mAccount . getDraftsFolderName ( ) ) ;
obsoleteOutbox . moveMessages ( messages , drafts ) ;
}
// Now get rid of the localized outbox
obsoleteOutbox . delete ( ) ;
obsoleteOutbox . delete ( true ) ;
}
} catch ( Exception e ) {
Log . e ( K9 . LOG_TAG , " Error trying to fix the outbox folders " , e ) ;
2011-06-17 00:17:01 -04:00
}
}
2012-10-08 16:51:29 -04:00
if ( db . getVersion ( ) < 44 ) {
try {
db . execSQL ( " ALTER TABLE messages ADD thread_root INTEGER " ) ;
db . execSQL ( " ALTER TABLE messages ADD thread_parent INTEGER " ) ;
db . execSQL ( " ALTER TABLE messages ADD normalized_subject_hash INTEGER " ) ;
db . execSQL ( " ALTER TABLE messages ADD empty INTEGER " ) ;
} catch ( SQLiteException e ) {
if ( ! e . getMessage ( ) . startsWith ( " duplicate column name: " ) ) {
throw e ;
}
}
}
2012-10-23 19:03:59 -04:00
if ( db . getVersion ( ) < 45 ) {
try {
db . execSQL ( " DROP INDEX IF EXISTS msg_empty " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS msg_thread_root " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_thread_root ON messages (thread_root) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS msg_thread_parent " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_thread_parent ON messages (thread_parent) " ) ;
} catch ( SQLiteException e ) {
if ( ! e . getMessage ( ) . startsWith ( " duplicate column name: " ) ) {
throw e ;
}
}
}
2012-12-03 23:13:58 -05:00
if ( db . getVersion ( ) < 46 ) {
db . execSQL ( " ALTER TABLE messages ADD read INTEGER default 0 " ) ;
db . execSQL ( " ALTER TABLE messages ADD flagged INTEGER default 0 " ) ;
db . execSQL ( " ALTER TABLE messages ADD answered INTEGER default 0 " ) ;
db . execSQL ( " ALTER TABLE messages ADD forwarded INTEGER default 0 " ) ;
String [ ] projection = { " id " , " flags " } ;
ContentValues cv = new ContentValues ( ) ;
List < Flag > extraFlags = new ArrayList < Flag > ( ) ;
Cursor cursor = db . query ( " messages " , projection , null , null , null , null , null ) ;
try {
while ( cursor . moveToNext ( ) ) {
long id = cursor . getLong ( 0 ) ;
String flagList = cursor . getString ( 1 ) ;
boolean read = false ;
boolean flagged = false ;
boolean answered = false ;
boolean forwarded = false ;
if ( flagList ! = null & & flagList . length ( ) > 0 ) {
String [ ] flags = flagList . split ( " , " ) ;
for ( String flagStr : flags ) {
try {
Flag flag = Flag . valueOf ( flagStr ) ;
switch ( flag ) {
case ANSWERED : {
answered = true ;
break ;
}
case DELETED : {
// Don't store this in column 'flags'
break ;
}
case FLAGGED : {
flagged = true ;
break ;
}
case FORWARDED : {
forwarded = true ;
break ;
}
case SEEN : {
read = true ;
break ;
}
case DRAFT :
case RECENT :
case X_DESTROYED :
case X_DOWNLOADED_FULL :
case X_DOWNLOADED_PARTIAL :
case X_GOT_ALL_HEADERS :
case X_REMOTE_COPY_STARTED :
case X_SEND_FAILED :
case X_SEND_IN_PROGRESS : {
extraFlags . add ( flag ) ;
break ;
}
}
} catch ( Exception e ) {
// Ignore bad flags
}
}
}
cv . put ( " flags " , serializeFlags ( extraFlags . toArray ( EMPTY_FLAG_ARRAY ) ) ) ;
cv . put ( " read " , read ) ;
cv . put ( " flagged " , flagged ) ;
cv . put ( " answered " , answered ) ;
cv . put ( " forwarded " , forwarded ) ;
db . update ( " messages " , cv , " id = ? " , new String [ ] { Long . toString ( id ) } ) ;
cv . clear ( ) ;
extraFlags . clear ( ) ;
}
} finally {
cursor . close ( ) ;
}
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_read ON messages (read) " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged) " ) ;
}
2013-01-10 21:40:35 -05:00
if ( db . getVersion ( ) < 47 ) {
// Create new 'threads' table
db . execSQL ( " DROP TABLE IF EXISTS threads " ) ;
db . execSQL ( " CREATE TABLE threads ( " +
" id INTEGER PRIMARY KEY, " +
" message_id INTEGER, " +
" root INTEGER, " +
" parent INTEGER " +
" ) " ) ;
// Create indices for new table
db . execSQL ( " DROP INDEX IF EXISTS threads_message_id " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS threads_root " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS threads_root ON threads (root) " ) ;
db . execSQL ( " DROP INDEX IF EXISTS threads_parent " ) ;
db . execSQL ( " CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent) " ) ;
// Create entries for all messages in 'threads' table
db . execSQL ( " INSERT INTO threads (message_id) SELECT id FROM messages " ) ;
// Copy thread structure from 'messages' table to 'threads'
Cursor cursor = db . query ( " messages " ,
new String [ ] { " id " , " thread_root " , " thread_parent " } ,
null , null , null , null , null ) ;
try {
ContentValues cv = new ContentValues ( ) ;
while ( cursor . moveToNext ( ) ) {
cv . clear ( ) ;
long messageId = cursor . getLong ( 0 ) ;
if ( ! cursor . isNull ( 1 ) ) {
long threadRootMessageId = cursor . getLong ( 1 ) ;
db . execSQL ( " UPDATE threads SET root = (SELECT t.id FROM " +
" threads t WHERE t.message_id = ?) " +
" WHERE message_id = ? " ,
new String [ ] {
Long . toString ( threadRootMessageId ) ,
Long . toString ( messageId )
} ) ;
}
if ( ! cursor . isNull ( 2 ) ) {
long threadParentMessageId = cursor . getLong ( 2 ) ;
db . execSQL ( " UPDATE threads SET parent = (SELECT t.id FROM " +
" threads t WHERE t.message_id = ?) " +
" WHERE message_id = ? " ,
new String [ ] {
Long . toString ( threadParentMessageId ) ,
Long . toString ( messageId )
} ) ;
}
}
} finally {
cursor . close ( ) ;
}
// Remove indices for old thread-related columns in 'messages' table
db . execSQL ( " DROP INDEX IF EXISTS msg_thread_root " ) ;
db . execSQL ( " DROP INDEX IF EXISTS msg_thread_parent " ) ;
// Clear out old thread-related columns in 'messages'
ContentValues cv = new ContentValues ( ) ;
cv . putNull ( " thread_root " ) ;
cv . putNull ( " thread_parent " ) ;
db . update ( " messages " , cv , null , null ) ;
}
2013-03-07 19:15:26 -05:00
if ( db . getVersion ( ) < 48 ) {
db . execSQL ( " UPDATE threads SET root=id WHERE root IS NULL " ) ;
db . execSQL ( " CREATE TRIGGER set_thread_root " +
" AFTER INSERT ON threads " +
" BEGIN " +
" UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
" END " ) ;
}
2011-06-17 00:17:01 -04:00
}
2012-07-07 11:14:03 -04:00
} catch ( SQLiteException e ) {
Log . e ( K9 . LOG_TAG , " Exception while upgrading database. Resetting the DB to v0 " ) ;
db . setVersion ( 0 ) ;
throw new Error ( " Database upgrade failed! Resetting your DB version to 0 to force a full schema recreation. " ) ;
2011-01-15 23:23:03 -05:00
}
2010-01-24 15:41:04 -05:00
2012-07-07 11:14:03 -04:00
db . setVersion ( DB_VERSION ) ;
2009-11-17 11:54:50 -05:00
2012-07-07 11:14:03 -04:00
db . setTransactionSuccessful ( ) ;
2012-05-30 06:45:37 -04:00
} finally {
db . endTransaction ( ) ;
}
2012-07-07 11:15:14 -04:00
if ( db . getVersion ( ) ! = DB_VERSION ) {
throw new Error ( " Database upgrade failed! " ) ;
}
2010-12-24 13:55:05 -05:00
// Unless we're blowing away the whole data store, there's no reason to prune attachments
// every time the user upgrades. it'll just cost them money and pain.
// try
//{
// pruneCachedAttachments(true);
//}
//catch (Exception me)
//{
// Log.e(K9.LOG_TAG, "Exception while force pruning attachments during DB update", me);
//}
2009-11-24 19:40:29 -05:00
}
2011-01-15 23:23:03 -05:00
2011-02-06 17:09:48 -05:00
private void update41Metadata ( final SQLiteDatabase db , SharedPreferences prefs , int id , String name ) {
2011-01-15 23:23:03 -05:00
Folder . FolderClass displayClass = Folder . FolderClass . NO_CLASS ;
Folder . FolderClass syncClass = Folder . FolderClass . INHERITED ;
Folder . FolderClass pushClass = Folder . FolderClass . SECOND_CLASS ;
boolean inTopGroup = false ;
boolean integrate = false ;
2011-04-05 05:27:39 -04:00
if ( mAccount . getInboxFolderName ( ) . equals ( name ) ) {
2011-01-15 23:23:03 -05:00
displayClass = Folder . FolderClass . FIRST_CLASS ;
syncClass = Folder . FolderClass . FIRST_CLASS ;
pushClass = Folder . FolderClass . FIRST_CLASS ;
inTopGroup = true ;
integrate = true ;
}
2011-02-06 17:09:48 -05:00
try {
displayClass = Folder . FolderClass . valueOf ( prefs . getString ( uUid + " . " + name + " .displayMode " , displayClass . name ( ) ) ) ;
syncClass = Folder . FolderClass . valueOf ( prefs . getString ( uUid + " . " + name + " .syncMode " , syncClass . name ( ) ) ) ;
pushClass = Folder . FolderClass . valueOf ( prefs . getString ( uUid + " . " + name + " .pushMode " , pushClass . name ( ) ) ) ;
inTopGroup = prefs . getBoolean ( uUid + " . " + name + " .inTopGroup " , inTopGroup ) ;
integrate = prefs . getBoolean ( uUid + " . " + name + " .integrate " , integrate ) ;
} catch ( Exception e ) {
2011-11-03 01:18:30 -04:00
Log . e ( K9 . LOG_TAG , " Throwing away an error while trying to upgrade folder metadata " , e ) ;
2011-01-15 23:23:03 -05:00
}
2011-02-06 17:09:48 -05:00
if ( displayClass = = Folder . FolderClass . NONE ) {
2011-01-15 23:23:03 -05:00
displayClass = Folder . FolderClass . NO_CLASS ;
}
2011-02-06 17:09:48 -05:00
if ( syncClass = = Folder . FolderClass . NONE ) {
2011-01-15 23:23:03 -05:00
syncClass = Folder . FolderClass . INHERITED ;
}
2011-02-06 17:09:48 -05:00
if ( pushClass = = Folder . FolderClass . NONE ) {
2011-01-15 23:23:03 -05:00
pushClass = Folder . FolderClass . INHERITED ;
}
db . execSQL ( " UPDATE folders SET integrate = ?, top_group = ?, poll_class=?, push_class =?, display_class = ? WHERE id = ? " ,
2011-02-06 17:09:48 -05:00
new Object [ ] { integrate , inTopGroup , syncClass , pushClass , displayClass , id } ) ;
2011-01-15 23:23:03 -05:00
}
2010-12-18 17:56:40 -05:00
}
2009-11-24 19:40:29 -05:00
2011-01-15 23:23:03 -05:00
2011-02-06 17:09:48 -05:00
public long getSize ( ) throws UnavailableStorageException {
2009-11-24 19:40:29 -05:00
2010-11-13 16:40:56 -05:00
final StorageManager storageManager = StorageManager . getInstance ( mApplication ) ;
final File attachmentDirectory = storageManager . getAttachmentDirectory ( uUid ,
2010-12-18 17:56:40 -05:00
database . getStorageProviderId ( ) ) ;
2010-11-13 16:40:56 -05:00
2011-02-06 17:09:48 -05:00
return database . execute ( false , new DbCallback < Long > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Long doDbWork ( final SQLiteDatabase db ) {
2010-11-13 16:40:56 -05:00
final File [ ] files = attachmentDirectory . listFiles ( ) ;
long attachmentLength = 0 ;
2011-02-06 17:09:48 -05:00
for ( File file : files ) {
if ( file . exists ( ) ) {
2010-11-13 16:40:56 -05:00
attachmentLength + = file . length ( ) ;
}
}
2010-02-17 22:28:31 -05:00
2010-12-18 17:56:40 -05:00
final File dbFile = storageManager . getDatabase ( uUid , database . getStorageProviderId ( ) ) ;
2010-11-13 16:40:56 -05:00
return dbFile . length ( ) + attachmentLength ;
}
} ) ;
2009-02-09 22:18:42 -05:00
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
public void compact ( ) throws MessagingException {
2010-04-21 22:20:35 -04:00
if ( K9 . DEBUG )
2010-12-01 01:04:28 -05:00
Log . i ( K9 . LOG_TAG , " Before compaction size = " + getSize ( ) ) ;
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
db . execSQL ( " VACUUM " ) ;
return null ;
}
} ) ;
2010-04-21 22:20:35 -04:00
if ( K9 . DEBUG )
Log . i ( K9 . LOG_TAG , " After compaction size = " + getSize ( ) ) ;
2009-02-09 22:18:42 -05:00
}
2008-11-01 17:32:06 -04:00
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
public void clear ( ) throws MessagingException {
2010-04-21 22:20:35 -04:00
if ( K9 . DEBUG )
Log . i ( K9 . LOG_TAG , " Before prune size = " + getSize ( ) ) ;
2009-11-24 19:40:29 -05:00
pruneCachedAttachments ( true ) ;
2011-02-06 17:09:48 -05:00
if ( K9 . DEBUG ) {
2010-04-21 22:20:35 -04:00
Log . i ( K9 . LOG_TAG , " After prune / before compaction size = " + getSize ( ) ) ;
2009-11-24 19:40:29 -05:00
2010-04-21 22:20:35 -04:00
Log . i ( K9 . LOG_TAG , " Before clear folder count = " + getFolderCount ( ) ) ;
Log . i ( K9 . LOG_TAG , " Before clear message count = " + getMessageCount ( ) ) ;
2009-11-24 19:40:29 -05:00
2010-04-21 22:20:35 -04:00
Log . i ( K9 . LOG_TAG , " After prune / before clear size = " + getSize ( ) ) ;
}
2009-11-24 19:40:29 -05:00
// don't delete messages that are Local, since there is no copy on the server.
// Don't delete deleted messages. They are essentially placeholders for UIDs of messages that have
2009-11-29 23:57:29 -05:00
// been deleted locally. They take up insignificant space
2011-02-06 17:09:48 -05:00
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) {
2013-01-10 21:40:35 -05:00
// Delete entries from 'threads' table
db . execSQL ( " DELETE FROM threads WHERE message_id IN " +
" (SELECT id FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%') " ) ;
// Set 'root' and 'parent' of remaining entries in 'thread' table to 'NULL' to make
// sure the thread structure is in a valid state (this may destroy existing valid
// thread trees, but is much faster than adjusting the tree by removing messages
// one by one).
2013-03-07 19:15:26 -05:00
db . execSQL ( " UPDATE threads SET root=id, parent=NULL " ) ;
2013-01-10 21:40:35 -05:00
// Delete entries from 'messages' table
db . execSQL ( " DELETE FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%' " ) ;
2010-11-13 16:40:56 -05:00
return null ;
}
} ) ;
2009-11-24 19:40:29 -05:00
compact ( ) ;
2010-11-13 16:40:56 -05:00
2011-02-06 17:09:48 -05:00
if ( K9 . DEBUG ) {
2010-04-21 22:20:35 -04:00
Log . i ( K9 . LOG_TAG , " After clear message count = " + getMessageCount ( ) ) ;
2009-11-24 19:40:29 -05:00
2010-04-21 22:20:35 -04:00
Log . i ( K9 . LOG_TAG , " After clear size = " + getSize ( ) ) ;
}
2009-02-09 22:18:42 -05:00
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
public int getMessageCount ( ) throws MessagingException {
return database . execute ( false , new DbCallback < Integer > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Integer doDbWork ( final SQLiteDatabase db ) {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery ( " SELECT COUNT(*) FROM messages " , null ) ;
cursor . moveToFirst ( ) ;
2010-12-28 04:10:30 -05:00
return cursor . getInt ( 0 ) ; // message count
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
2009-11-24 19:40:29 -05:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2009-11-24 19:40:29 -05:00
}
2011-02-06 17:09:48 -05:00
public int getFolderCount ( ) throws MessagingException {
return database . execute ( false , new DbCallback < Integer > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Integer doDbWork ( final SQLiteDatabase db ) {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery ( " SELECT COUNT(*) FROM folders " , null ) ;
cursor . moveToFirst ( ) ;
2010-12-28 04:10:30 -05:00
return cursor . getInt ( 0 ) ; // folder count
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
2009-02-09 22:18:42 -05:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2009-02-09 22:18:42 -05:00
}
2009-11-24 19:40:29 -05:00
2008-11-01 17:32:06 -04:00
@Override
2011-02-06 17:09:48 -05:00
public LocalFolder getFolder ( String name ) {
2008-11-01 17:32:06 -04:00
return new LocalFolder ( name ) ;
}
2012-10-17 23:15:40 -04:00
public LocalFolder getFolderById ( long folderId ) {
return new LocalFolder ( folderId ) ;
}
2008-11-01 17:32:06 -04:00
// TODO this takes about 260-300ms, seems slow.
@Override
2011-02-06 17:09:48 -05:00
public List < ? extends Folder > getPersonalNamespaces ( boolean forceListAll ) throws MessagingException {
2010-11-13 16:40:56 -05:00
final List < LocalFolder > folders = new LinkedList < LocalFolder > ( ) ;
2011-02-06 17:09:48 -05:00
try {
database . execute ( false , new DbCallback < List < ? extends Folder > > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public List < ? extends Folder > doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
try {
2012-12-07 09:04:53 -05:00
cursor = db . rawQuery ( " SELECT " + GET_FOLDER_COLS + " FROM folders " +
" LEFT JOIN messages ON (folder_id = folders.id AND " +
" (empty IS NULL OR empty != 1) AND deleted = 0) " +
" GROUP BY folders.id ORDER BY name ASC " , null ) ;
2011-02-06 17:09:48 -05:00
while ( cursor . moveToNext ( ) ) {
2012-12-10 11:34:48 -05:00
if ( cursor . isNull ( 0 ) ) {
continue ;
}
2010-11-13 16:40:56 -05:00
LocalFolder folder = new LocalFolder ( cursor . getString ( 1 ) ) ;
2011-01-15 23:23:03 -05:00
folder . open ( cursor . getInt ( 0 ) , cursor . getString ( 1 ) , cursor . getInt ( 2 ) , cursor . getInt ( 3 ) , cursor . getLong ( 4 ) , cursor . getString ( 5 ) , cursor . getString ( 6 ) , cursor . getLong ( 7 ) , cursor . getInt ( 8 ) , cursor . getInt ( 9 ) , cursor . getInt ( 10 ) , cursor . getString ( 11 ) , cursor . getString ( 12 ) , cursor . getString ( 13 ) ) ;
2010-11-13 16:40:56 -05:00
folders . add ( folder ) ;
}
return folders ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
2010-04-21 22:20:35 -04:00
return folders ;
2008-11-01 17:32:06 -04:00
}
@Override
2011-02-06 17:09:48 -05:00
public void checkSettings ( ) throws MessagingException {
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public void delete ( ) throws UnavailableStorageException {
2010-12-18 17:56:40 -05:00
database . delete ( ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
public void recreate ( ) throws UnavailableStorageException {
2010-12-18 17:56:40 -05:00
database . recreate ( ) ;
2008-11-01 17:32:06 -04:00
}
2010-05-15 15:46:16 -04:00
2011-02-06 17:09:48 -05:00
public void pruneCachedAttachments ( ) throws MessagingException {
2009-11-24 19:40:29 -05:00
pruneCachedAttachments ( false ) ;
2009-02-09 22:18:42 -05:00
}
2009-11-24 19:40:29 -05:00
2010-02-17 22:28:31 -05:00
/ * *
* Deletes all cached attachments for the entire store .
2010-12-28 04:10:50 -05:00
* @param force
* @throws com . fsck . k9 . mail . MessagingException
2010-02-17 22:28:31 -05:00
* /
2011-02-06 17:09:48 -05:00
private void pruneCachedAttachments ( final boolean force ) throws MessagingException {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
if ( force ) {
2010-11-13 16:40:56 -05:00
ContentValues cv = new ContentValues ( ) ;
cv . putNull ( " content_uri " ) ;
db . update ( " attachments " , cv , null , null ) ;
}
final StorageManager storageManager = StorageManager . getInstance ( mApplication ) ;
2010-12-18 17:56:40 -05:00
File [ ] files = storageManager . getAttachmentDirectory ( uUid , database . getStorageProviderId ( ) ) . listFiles ( ) ;
2011-02-06 17:09:48 -05:00
for ( File file : files ) {
if ( file . exists ( ) ) {
if ( ! force ) {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . query (
" attachments " ,
new String [ ] { " store_data " } ,
" id = ? " ,
new String [ ] { file . getName ( ) } ,
null ,
null ,
null ) ;
2011-02-06 17:09:48 -05:00
if ( cursor . moveToNext ( ) ) {
if ( cursor . getString ( 0 ) = = null ) {
2010-11-13 16:40:56 -05:00
if ( K9 . DEBUG )
Log . d ( K9 . LOG_TAG , " Attachment " + file . getAbsolutePath ( ) + " has no store data, not deleting " ) ;
/ *
* If the attachment has no store data it is not recoverable , so
* we won ' t delete it .
* /
continue ;
}
}
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
}
2011-02-06 17:09:48 -05:00
if ( ! force ) {
try {
2010-11-13 16:40:56 -05:00
ContentValues cv = new ContentValues ( ) ;
cv . putNull ( " content_uri " ) ;
db . update ( " attachments " , cv , " id = ? " , new String [ ] { file . getName ( ) } ) ;
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2010-02-17 22:28:31 -05:00
/ *
2010-11-13 16:40:56 -05:00
* If the row has gone away before we got to mark it not - downloaded that ' s
* okay .
2010-02-17 22:28:31 -05:00
* /
2010-02-11 16:16:37 -05:00
}
2009-11-24 19:40:29 -05:00
}
2011-02-06 17:09:48 -05:00
if ( ! file . delete ( ) ) {
2010-11-13 16:40:56 -05:00
file . deleteOnExit ( ) ;
2010-02-11 16:16:37 -05:00
}
2009-11-24 19:40:29 -05:00
}
2010-02-17 22:28:31 -05:00
}
2010-11-13 16:40:56 -05:00
return null ;
2008-11-01 17:32:06 -04:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public void resetVisibleLimits ( ) throws UnavailableStorageException {
2010-05-30 17:20:47 -04:00
resetVisibleLimits ( mAccount . getDisplayCount ( ) ) ;
2008-12-11 00:25:59 -05:00
}
2011-02-06 17:09:48 -05:00
public void resetVisibleLimits ( int visibleLimit ) throws UnavailableStorageException {
2010-11-13 16:40:56 -05:00
final ContentValues cv = new ContentValues ( ) ;
2008-12-11 00:25:59 -05:00
cv . put ( " visible_limit " , Integer . toString ( visibleLimit ) ) ;
2011-02-06 17:09:48 -05:00
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
db . update ( " folders " , cv , null , null ) ;
return null ;
}
} ) ;
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public ArrayList < PendingCommand > getPendingCommands ( ) throws UnavailableStorageException {
return database . execute ( false , new DbCallback < ArrayList < PendingCommand > > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public ArrayList < PendingCommand > doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . query ( " pending_commands " ,
new String [ ] { " id " , " command " , " arguments " } ,
null ,
null ,
null ,
null ,
" id ASC " ) ;
ArrayList < PendingCommand > commands = new ArrayList < PendingCommand > ( ) ;
2011-02-06 17:09:48 -05:00
while ( cursor . moveToNext ( ) ) {
2010-11-13 16:40:56 -05:00
PendingCommand command = new PendingCommand ( ) ;
command . mId = cursor . getLong ( 0 ) ;
command . command = cursor . getString ( 1 ) ;
String arguments = cursor . getString ( 2 ) ;
command . arguments = arguments . split ( " , " ) ;
2011-02-06 17:09:48 -05:00
for ( int i = 0 ; i < command . arguments . length ; i + + ) {
2010-11-13 16:40:56 -05:00
command . arguments [ i ] = Utility . fastUrlDecode ( command . arguments [ i ] ) ;
}
commands . add ( command ) ;
}
return commands ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2008-11-01 17:32:06 -04:00
}
}
2010-11-13 16:40:56 -05:00
} ) ;
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public void addPendingCommand ( PendingCommand command ) throws UnavailableStorageException {
try {
for ( int i = 0 ; i < command . arguments . length ; i + + ) {
2008-11-01 17:32:06 -04:00
command . arguments [ i ] = URLEncoder . encode ( command . arguments [ i ] , " UTF-8 " ) ;
}
2010-11-13 16:40:56 -05:00
final ContentValues cv = new ContentValues ( ) ;
2008-11-01 17:32:06 -04:00
cv . put ( " command " , command . command ) ;
cv . put ( " arguments " , Utility . combine ( command . arguments , ',' ) ) ;
2011-02-06 17:09:48 -05:00
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
db . insert ( " pending_commands " , " command " , cv ) ;
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( UnsupportedEncodingException usee ) {
2008-11-01 17:32:06 -04:00
throw new Error ( " Aparently UTF-8 has been lost to the annals of history. " ) ;
}
}
2011-02-06 17:09:48 -05:00
public void removePendingCommand ( final PendingCommand command ) throws UnavailableStorageException {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
db . delete ( " pending_commands " , " id = ? " , new String [ ] { Long . toString ( command . mId ) } ) ;
return null ;
}
} ) ;
}
2011-02-06 17:09:48 -05:00
public void removePendingCommands ( ) throws UnavailableStorageException {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
db . delete ( " pending_commands " , null , null ) ;
return null ;
}
} ) ;
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public static class PendingCommand {
2008-11-01 17:32:06 -04:00
private long mId ;
public String command ;
public String [ ] arguments ;
@Override
2011-02-06 17:09:48 -05:00
public String toString ( ) {
2011-09-30 02:18:00 -04:00
StringBuilder sb = new StringBuilder ( ) ;
2008-11-01 17:32:06 -04:00
sb . append ( command ) ;
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
sb . append ( " : " ) ;
2011-02-06 17:09:48 -05:00
for ( String argument : arguments ) {
2009-11-29 11:55:35 -05:00
sb . append ( " , " ) ;
2008-11-01 17:32:06 -04:00
sb . append ( argument ) ;
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
//sb.append("\n");
2008-11-01 17:32:06 -04:00
}
return sb . toString ( ) ;
}
}
2009-11-24 19:40:29 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public boolean isMoveCapable ( ) {
2009-11-24 19:40:29 -05:00
return true ;
2009-03-05 02:32:45 -05:00
}
2009-12-27 11:53:37 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public boolean isCopyCapable ( ) {
2009-11-24 19:40:29 -05:00
return true ;
2009-03-05 02:32:45 -05:00
}
2008-11-01 17:32:06 -04:00
2012-10-16 16:42:51 -04:00
public Message [ ] searchForMessages ( MessageRetrievalListener retrievalListener ,
LocalSearch search ) throws MessagingException {
2012-10-28 21:27:34 -04:00
StringBuilder query = new StringBuilder ( ) ;
List < String > queryArgs = new ArrayList < String > ( ) ;
SqlQueryBuilder . buildWhereClause ( mAccount , search . getConditions ( ) , query , queryArgs ) ;
2012-10-16 16:42:51 -04:00
2012-11-02 20:52:45 -04:00
// Avoid "ambiguous column name" error by prefixing "id" with the message table name
String where = SqlQueryBuilder . addPrefixToSelection ( new String [ ] { " id " } ,
" messages. " , query . toString ( ) ) ;
2012-10-28 21:27:34 -04:00
String [ ] selectionArgs = queryArgs . toArray ( EMPTY_STRING_ARRAY ) ;
2012-10-16 16:42:51 -04:00
2012-10-30 20:45:44 -04:00
String sqlQuery = " SELECT " + GET_MESSAGES_COLS + " FROM messages " +
2013-01-11 22:21:53 -05:00
" LEFT JOIN threads ON (threads.message_id = messages.id) " +
2012-11-01 15:33:13 -04:00
" LEFT JOIN folders ON (folders.id = messages.folder_id) WHERE " +
2012-10-24 00:01:26 -04:00
" ((empty IS NULL OR empty != 1) AND deleted = 0) " +
2012-10-28 21:27:34 -04:00
( ( ! StringUtils . isNullOrEmpty ( where ) ) ? " AND ( " + where + " ) " : " " ) +
" ORDER BY date DESC " ;
2012-10-16 16:42:51 -04:00
2011-02-06 17:09:48 -05:00
if ( K9 . DEBUG ) {
2012-10-13 08:53:00 -04:00
Log . d ( K9 . LOG_TAG , " Query = " + sqlQuery ) ;
2010-04-21 22:20:35 -04:00
}
2012-10-16 16:42:51 -04:00
2012-10-28 21:27:34 -04:00
return getMessages ( retrievalListener , null , sqlQuery , selectionArgs ) ;
2012-10-16 16:42:51 -04:00
}
2009-12-27 11:53:37 -05:00
/ *
* Given a query string , actually do the query for the messages and
* call the MessageRetrievalListener for each one
* /
private Message [ ] getMessages (
2010-11-13 16:40:56 -05:00
final MessageRetrievalListener listener ,
final LocalFolder folder ,
final String queryString , final String [ ] placeHolders
2011-02-06 17:09:48 -05:00
) throws MessagingException {
2010-11-13 16:40:56 -05:00
final ArrayList < LocalMessage > messages = new ArrayList < LocalMessage > ( ) ;
2011-02-06 17:09:48 -05:00
final int j = database . execute ( false , new DbCallback < Integer > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Integer doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
int i = 0 ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery ( queryString + " LIMIT 10 " , placeHolders ) ;
2009-12-27 11:53:37 -05:00
2011-02-06 17:09:48 -05:00
while ( cursor . moveToNext ( ) ) {
2010-11-13 16:40:56 -05:00
LocalMessage message = new LocalMessage ( null , folder ) ;
message . populateFromGetMessageCursor ( cursor ) ;
messages . add ( message ) ;
2011-02-06 17:09:48 -05:00
if ( listener ! = null ) {
2010-11-13 16:40:56 -05:00
listener . messageFinished ( message , i , - 1 ) ;
}
i + + ;
}
cursor . close ( ) ;
cursor = db . rawQuery ( queryString + " LIMIT -1 OFFSET 10 " , placeHolders ) ;
2011-02-06 17:09:48 -05:00
while ( cursor . moveToNext ( ) ) {
2010-11-13 16:40:56 -05:00
LocalMessage message = new LocalMessage ( null , folder ) ;
message . populateFromGetMessageCursor ( cursor ) ;
messages . add ( message ) ;
2011-02-06 17:09:48 -05:00
if ( listener ! = null ) {
2010-11-13 16:40:56 -05:00
listener . messageFinished ( message , i , - 1 ) ;
}
i + + ;
}
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2011-11-03 01:18:30 -04:00
Log . d ( K9 . LOG_TAG , " Got an exception " , e ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2009-12-27 11:53:37 -05:00
}
2010-11-13 16:40:56 -05:00
return i ;
2009-12-27 11:53:37 -05:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2011-02-06 17:09:48 -05:00
if ( listener ! = null ) {
2010-11-13 16:40:56 -05:00
listener . messagesFinished ( j ) ;
}
2010-10-21 16:48:28 -04:00
2010-11-13 16:40:56 -05:00
return messages . toArray ( EMPTY_MESSAGE_ARRAY ) ;
}
2012-11-02 20:52:45 -04:00
public Message [ ] getMessagesInThread ( final long rootId ) throws MessagingException {
String rootIdString = Long . toString ( rootId ) ;
LocalSearch search = new LocalSearch ( ) ;
2013-01-10 21:40:35 -05:00
search . and ( Searchfield . THREAD_ID , rootIdString , Attribute . EQUALS ) ;
2012-11-02 20:52:45 -04:00
return searchForMessages ( null , search ) ;
}
2011-02-06 17:09:48 -05:00
public AttachmentInfo getAttachmentInfo ( final String attachmentId ) throws UnavailableStorageException {
return database . execute ( false , new DbCallback < AttachmentInfo > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public AttachmentInfo doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-12-28 04:10:50 -05:00
String name ;
2011-03-24 18:07:46 -04:00
String type ;
2010-12-28 04:10:50 -05:00
int size ;
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . query (
" attachments " ,
2011-03-24 18:07:46 -04:00
new String [ ] { " name " , " size " , " mime_type " } ,
2010-11-13 16:40:56 -05:00
" id = ? " ,
new String [ ] { attachmentId } ,
null ,
null ,
null ) ;
2011-02-06 17:09:48 -05:00
if ( ! cursor . moveToFirst ( ) ) {
2010-11-13 16:40:56 -05:00
return null ;
}
name = cursor . getString ( 0 ) ;
size = cursor . getInt ( 1 ) ;
2011-03-24 18:07:46 -04:00
type = cursor . getString ( 2 ) ;
2010-11-13 16:40:56 -05:00
final AttachmentInfo attachmentInfo = new AttachmentInfo ( ) ;
attachmentInfo . name = name ;
attachmentInfo . size = size ;
2011-03-24 18:07:46 -04:00
attachmentInfo . type = type ;
2010-11-13 16:40:56 -05:00
return attachmentInfo ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
2009-12-27 11:53:37 -05:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2009-12-27 11:53:37 -05:00
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
public static class AttachmentInfo {
2010-11-13 16:40:56 -05:00
public String name ;
public int size ;
2011-03-24 18:07:46 -04:00
public String type ;
2010-11-13 16:40:56 -05:00
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
public void createFolders ( final List < LocalFolder > foldersToCreate , final int visibleLimit ) throws UnavailableStorageException {
database . execute ( true , new DbCallback < Void > ( ) {
2011-02-04 07:26:14 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
for ( LocalFolder folder : foldersToCreate ) {
2011-02-04 07:26:14 -05:00
String name = folder . getName ( ) ;
final LocalFolder . PreferencesHolder prefHolder = folder . new PreferencesHolder ( ) ;
// When created, special folders should always be displayed
// inbox should be integrated
// and the inbox and drafts folders should be syncced by default
2011-02-06 17:09:48 -05:00
if ( mAccount . isSpecialFolder ( name ) ) {
2011-02-04 07:26:14 -05:00
prefHolder . inTopGroup = true ;
prefHolder . displayClass = LocalFolder . FolderClass . FIRST_CLASS ;
2011-04-05 05:27:39 -04:00
if ( name . equalsIgnoreCase ( mAccount . getInboxFolderName ( ) ) ) {
2011-02-04 07:26:14 -05:00
prefHolder . integrate = true ;
prefHolder . pushClass = LocalFolder . FolderClass . FIRST_CLASS ;
2011-02-06 17:09:48 -05:00
} else {
2011-02-04 07:26:14 -05:00
prefHolder . pushClass = LocalFolder . FolderClass . INHERITED ;
}
2011-04-05 05:27:39 -04:00
if ( name . equalsIgnoreCase ( mAccount . getInboxFolderName ( ) ) | |
2011-02-06 17:09:48 -05:00
name . equalsIgnoreCase ( mAccount . getDraftsFolderName ( ) ) ) {
2011-02-04 07:26:14 -05:00
prefHolder . syncClass = LocalFolder . FolderClass . FIRST_CLASS ;
2011-02-06 17:09:48 -05:00
} else {
2011-02-04 07:26:14 -05:00
prefHolder . syncClass = LocalFolder . FolderClass . NO_CLASS ;
}
}
folder . refresh ( name , prefHolder ) ; // Recover settings from Preferences
2011-02-06 17:09:48 -05:00
db . execSQL ( " INSERT INTO folders (name, visible_limit, top_group, display_class, poll_class, push_class, integrate) VALUES (?, ?, ?, ?, ?, ?, ?) " , new Object [ ] {
name ,
2011-02-04 07:26:14 -05:00
visibleLimit ,
prefHolder . inTopGroup ? 1 : 0 ,
prefHolder . displayClass . name ( ) ,
prefHolder . syncClass . name ( ) ,
prefHolder . pushClass . name ( ) ,
prefHolder . integrate ? 1 : 0 ,
} ) ;
}
return null ;
}
} ) ;
}
2012-12-03 23:13:58 -05:00
private String serializeFlags ( Flag [ ] flags ) {
List < Flag > extraFlags = new ArrayList < Flag > ( ) ;
for ( Flag flag : flags ) {
switch ( flag ) {
case DELETED :
case SEEN :
case FLAGGED :
case ANSWERED :
case FORWARDED : {
break ;
}
default : {
extraFlags . add ( flag ) ;
}
}
}
return Utility . combine ( extraFlags . toArray ( EMPTY_FLAG_ARRAY ) , ',' ) . toUpperCase ( Locale . US ) ;
}
2011-02-06 17:09:48 -05:00
public class LocalFolder extends Folder implements Serializable {
2011-01-31 18:45:23 -05:00
/ * *
*
* /
2011-01-31 18:45:14 -05:00
private static final long serialVersionUID = - 1973296520918624767L ;
2009-12-27 12:22:26 -05:00
private String mName = null ;
2008-11-01 17:32:06 -04:00
private long mFolderId = - 1 ;
private int mUnreadMessageCount = - 1 ;
2010-04-16 10:33:54 -04:00
private int mFlaggedMessageCount = - 1 ;
2008-11-01 17:32:06 -04:00
private int mVisibleLimit = - 1 ;
2011-02-04 07:26:14 -05:00
private String prefId = null ;
2011-01-15 23:23:03 -05:00
private FolderClass mDisplayClass = FolderClass . NO_CLASS ;
private FolderClass mSyncClass = FolderClass . INHERITED ;
private FolderClass mPushClass = FolderClass . SECOND_CLASS ;
private boolean mInTopGroup = false ;
2009-11-24 19:40:29 -05:00
private String mPushState = null ;
2010-04-05 22:54:48 -04:00
private boolean mIntegrate = false ;
2011-01-02 04:01:23 -05:00
// mLastUid is used during syncs. It holds the highest UID within the local folder so we
// know whether or not an unread message added to the local folder is actually "new" or not.
private Integer mLastUid = null ;
2009-12-27 11:50:14 -05:00
2011-02-06 17:09:48 -05:00
public LocalFolder ( String name ) {
2010-03-03 23:00:30 -05:00
super ( LocalStore . this . mAccount ) ;
2008-11-01 17:32:06 -04:00
this . mName = name ;
2009-11-24 19:40:29 -05:00
2011-04-05 05:27:39 -04:00
if ( LocalStore . this . mAccount . getInboxFolderName ( ) . equals ( getName ( ) ) ) {
2011-01-15 23:23:03 -05:00
mSyncClass = FolderClass . FIRST_CLASS ;
mPushClass = FolderClass . FIRST_CLASS ;
mInTopGroup = true ;
2009-01-02 20:47:24 -05:00
}
2009-11-24 19:40:29 -05:00
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public LocalFolder ( long id ) {
2010-03-03 23:00:30 -05:00
super ( LocalStore . this . mAccount ) ;
2009-12-27 11:53:16 -05:00
this . mFolderId = id ;
}
2011-02-06 17:09:48 -05:00
public long getId ( ) {
2008-11-01 17:32:06 -04:00
return mFolderId ;
}
@Override
2011-02-06 17:09:48 -05:00
public void open ( final OpenMode mode ) throws MessagingException {
2012-03-05 15:04:34 -05:00
if ( isOpen ( ) & & ( getMode ( ) = = mode | | mode = = OpenMode . READ_ONLY ) ) {
2009-11-24 19:40:29 -05:00
return ;
2012-03-05 15:04:34 -05:00
} else if ( isOpen ( ) ) {
//previously opened in READ_ONLY and now requesting READ_WRITE
//so close connection and reopen
close ( ) ;
2009-11-24 19:40:29 -05:00
}
2012-03-05 15:04:34 -05:00
2011-02-06 17:09:48 -05:00
try {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2012-12-07 09:04:53 -05:00
String baseQuery = " SELECT " + GET_FOLDER_COLS + " FROM folders " +
" LEFT JOIN messages ON (folder_id = folders.id AND " +
" (empty IS NULL OR empty != 1) AND deleted = 0) " ;
2011-01-15 23:23:03 -05:00
2011-02-06 17:09:48 -05:00
if ( mName ! = null ) {
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery ( baseQuery + " where folders.name = ? " , new String [ ] { mName } ) ;
2011-02-06 17:09:48 -05:00
} else {
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery ( baseQuery + " where folders.id = ? " , new String [ ] { Long . toString ( mFolderId ) } ) ;
}
2012-12-10 11:34:48 -05:00
if ( cursor . moveToFirst ( ) & & ! cursor . isNull ( 0 ) ) {
2010-11-13 16:40:56 -05:00
int folderId = cursor . getInt ( 0 ) ;
2011-02-06 17:09:48 -05:00
if ( folderId > 0 ) {
2011-01-15 23:23:03 -05:00
open ( folderId , cursor . getString ( 1 ) , cursor . getInt ( 2 ) , cursor . getInt ( 3 ) , cursor . getLong ( 4 ) , cursor . getString ( 5 ) , cursor . getString ( 6 ) , cursor . getLong ( 7 ) , cursor . getInt ( 8 ) , cursor . getInt ( 9 ) , cursor . getInt ( 10 ) , cursor . getString ( 11 ) , cursor . getString ( 12 ) , cursor . getString ( 13 ) ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
} else {
2010-11-13 16:40:56 -05:00
Log . w ( K9 . LOG_TAG , " Creating folder " + getName ( ) + " with existing id " + getId ( ) ) ;
create ( FolderType . HOLDS_MESSAGES ) ;
open ( mode ) ;
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
return null ;
2009-11-24 19:40:29 -05:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
private void open ( int id , String name , int unreadCount , int visibleLimit , long lastChecked , String status , String pushState , long lastPushed , int flaggedCount , int integrate , int topGroup , String syncClass , String pushClass , String displayClass ) throws MessagingException {
2009-11-24 19:40:29 -05:00
mFolderId = id ;
2009-12-27 11:53:16 -05:00
mName = name ;
2009-11-24 19:40:29 -05:00
mUnreadMessageCount = unreadCount ;
mVisibleLimit = visibleLimit ;
mPushState = pushState ;
2010-04-16 10:33:54 -04:00
mFlaggedMessageCount = flaggedCount ;
2009-11-24 19:40:29 -05:00
super . setStatus ( status ) ;
// Only want to set the local variable stored in the super class. This class
// does a DB update on setLastChecked
super . setLastChecked ( lastChecked ) ;
super . setLastPush ( lastPushed ) ;
2011-02-06 17:09:48 -05:00
mInTopGroup = topGroup = = 1 ? true : false ;
2011-01-15 23:23:03 -05:00
mIntegrate = integrate = = 1 ? true : false ;
2011-01-18 20:50:28 -05:00
String noClass = FolderClass . NO_CLASS . toString ( ) ;
mDisplayClass = Folder . FolderClass . valueOf ( ( displayClass = = null ) ? noClass : displayClass ) ;
mPushClass = Folder . FolderClass . valueOf ( ( pushClass = = null ) ? noClass : pushClass ) ;
mSyncClass = Folder . FolderClass . valueOf ( ( syncClass = = null ) ? noClass : syncClass ) ;
2011-01-15 23:23:03 -05:00
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
}
2008-11-01 17:32:06 -04:00
@Override
2011-02-06 17:09:48 -05:00
public boolean isOpen ( ) {
2009-12-27 12:22:26 -05:00
return ( mFolderId ! = - 1 & & mName ! = null ) ;
2008-11-01 17:32:06 -04:00
}
@Override
2011-02-06 17:09:48 -05:00
public OpenMode getMode ( ) {
2008-11-01 17:32:06 -04:00
return OpenMode . READ_WRITE ;
}
@Override
2011-02-06 17:09:48 -05:00
public String getName ( ) {
2008-11-01 17:32:06 -04:00
return mName ;
}
@Override
2011-02-06 17:09:48 -05:00
public boolean exists ( ) throws MessagingException {
return database . execute ( false , new DbCallback < Boolean > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Boolean doDbWork ( final SQLiteDatabase db ) throws WrappedException {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery ( " SELECT id FROM folders "
+ " where folders.name = ? " , new String [ ] { LocalFolder . this
. getName ( )
} ) ;
2011-02-06 17:09:48 -05:00
if ( cursor . moveToFirst ( ) ) {
2010-11-13 16:40:56 -05:00
int folderId = cursor . getInt ( 0 ) ;
return ( folderId > 0 ) ;
}
2012-12-07 09:50:55 -05:00
return false ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
2008-11-01 23:56:13 -04:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2008-11-01 23:56:13 -04:00
}
2008-11-01 17:32:06 -04:00
@Override
2011-02-06 17:09:48 -05:00
public boolean create ( FolderType type ) throws MessagingException {
2011-01-17 19:04:17 -05:00
return create ( type , mAccount . getDisplayCount ( ) ) ;
2008-11-01 17:32:06 -04:00
}
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public boolean create ( FolderType type , final int visibleLimit ) throws MessagingException {
if ( exists ( ) ) {
2008-12-11 00:25:59 -05:00
throw new MessagingException ( " Folder " + mName + " already exists. " ) ;
}
2011-02-04 07:26:14 -05:00
List < LocalFolder > foldersToCreate = new ArrayList < LocalFolder > ( 1 ) ;
foldersToCreate . add ( this ) ;
LocalStore . this . createFolders ( foldersToCreate , visibleLimit ) ;
2011-01-17 19:04:24 -05:00
2008-12-11 00:25:59 -05:00
return true ;
}
2011-02-06 17:09:48 -05:00
private class PreferencesHolder {
2011-02-04 07:26:14 -05:00
FolderClass displayClass = mDisplayClass ;
FolderClass syncClass = mSyncClass ;
FolderClass pushClass = mPushClass ;
boolean inTopGroup = mInTopGroup ;
boolean integrate = mIntegrate ;
}
2008-11-01 17:32:06 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void close ( ) {
2008-11-01 17:32:06 -04:00
mFolderId = - 1 ;
}
@Override
2011-02-06 17:09:48 -05:00
public int getMessageCount ( ) throws MessagingException {
try {
return database . execute ( false , new DbCallback < Integer > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Integer doDbWork ( final SQLiteDatabase db ) throws WrappedException {
try {
2010-11-13 16:40:56 -05:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2012-10-08 16:51:29 -04:00
cursor = db . rawQuery ( " SELECT COUNT(*) FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 and folder_id = ? " ,
2011-02-06 17:09:48 -05:00
new String [ ] {
2010-11-13 16:40:56 -05:00
Long . toString ( mFolderId )
} ) ;
cursor . moveToFirst ( ) ;
2010-12-28 04:10:30 -05:00
return cursor . getInt ( 0 ) ; //messagecount
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
}
@Override
2011-02-06 17:09:48 -05:00
public int getUnreadMessageCount ( ) throws MessagingException {
2012-12-07 09:45:36 -05:00
if ( ! isOpen ( ) ) {
// open() sums up the number of unread messages in the database
open ( OpenMode . READ_WRITE ) ;
return mUnreadMessageCount ;
}
// Folder was already opened. Unread count might be outdated so query the database now.
try {
return database . execute ( false , new DbCallback < Integer > ( ) {
@Override
public Integer doDbWork ( final SQLiteDatabase db ) throws WrappedException {
int unreadMessageCount = 0 ;
Cursor cursor = db . query ( " messages " , new String [ ] { " SUM(read=0) " } ,
" folder_id = ? AND (empty IS NULL OR empty != 1) AND deleted = 0 " ,
new String [ ] { Long . toString ( mFolderId ) } , null , null , null ) ;
try {
if ( cursor . moveToFirst ( ) ) {
unreadMessageCount = cursor . getInt ( 0 ) ;
}
} finally {
cursor . close ( ) ;
}
return unreadMessageCount ;
}
} ) ;
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
}
2008-11-01 17:32:06 -04:00
}
2010-04-29 00:59:14 -04:00
2010-04-16 10:33:54 -04:00
@Override
2011-02-06 17:09:48 -05:00
public int getFlaggedMessageCount ( ) throws MessagingException {
2010-04-16 10:33:54 -04:00
open ( OpenMode . READ_WRITE ) ;
return mFlaggedMessageCount ;
}
2008-11-01 17:32:06 -04:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setLastChecked ( final long lastChecked ) throws MessagingException {
try {
2011-01-15 23:22:59 -05:00
open ( OpenMode . READ_WRITE ) ;
LocalFolder . super . setLastChecked ( lastChecked ) ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2011-01-15 23:22:59 -05:00
throw new WrappedException ( e ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " last_updated " , lastChecked ) ;
2009-11-24 19:40:29 -05:00
}
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setLastPush ( final long lastChecked ) throws MessagingException {
try {
2011-01-15 23:22:59 -05:00
open ( OpenMode . READ_WRITE ) ;
LocalFolder . super . setLastPush ( lastChecked ) ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2011-01-15 23:22:59 -05:00
throw new WrappedException ( e ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " last_pushed " , lastChecked ) ;
2009-10-21 20:41:06 -04:00
}
2008-11-01 17:32:06 -04:00
2011-02-06 17:09:48 -05:00
public int getVisibleLimit ( ) throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
return mVisibleLimit ;
}
2011-02-06 17:09:48 -05:00
public void purgeToVisibleLimit ( MessageRemovalListener listener ) throws MessagingException {
2012-03-05 15:04:34 -05:00
//don't purge messages while a Search is active since it might throw away search results
if ( ! Search . isActive ( ) ) {
if ( mVisibleLimit = = 0 ) {
return ;
}
open ( OpenMode . READ_WRITE ) ;
Message [ ] messages = getMessages ( null , false ) ;
for ( int i = mVisibleLimit ; i < messages . length ; i + + ) {
if ( listener ! = null ) {
listener . messageRemoved ( messages [ i ] ) ;
}
messages [ i ] . destroy ( ) ;
2009-11-26 00:10:12 -05:00
}
}
}
2008-11-01 17:32:06 -04:00
2011-02-06 17:09:48 -05:00
public void setVisibleLimit ( final int visibleLimit ) throws MessagingException {
2011-01-15 23:22:59 -05:00
mVisibleLimit = visibleLimit ;
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " visible_limit " , mVisibleLimit ) ;
2008-11-01 17:32:06 -04:00
}
2009-11-24 19:40:29 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setStatus ( final String status ) throws MessagingException {
updateFolderColumn ( " status " , status ) ;
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
}
2011-02-06 17:09:48 -05:00
public void setPushState ( final String pushState ) throws MessagingException {
2011-01-15 23:22:59 -05:00
mPushState = pushState ;
updateFolderColumn ( " push_state " , pushState ) ;
}
2011-02-06 17:09:48 -05:00
private void updateFolderColumn ( final String column , final Object value ) throws MessagingException {
try {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
try {
2010-11-13 16:40:56 -05:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
2011-02-06 17:09:48 -05:00
db . execSQL ( " UPDATE folders SET " + column + " = ? WHERE id = ? " , new Object [ ] { value , mFolderId } ) ;
2010-11-13 16:40:56 -05:00
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2010-11-13 16:40:56 -05:00
}
2009-10-21 20:41:06 -04:00
}
2011-02-04 07:26:14 -05:00
2011-02-06 17:09:48 -05:00
public String getPushState ( ) {
2009-10-21 20:41:06 -04:00
return mPushState ;
}
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
@Override
2011-02-06 17:09:48 -05:00
public FolderClass getDisplayClass ( ) {
2011-01-15 23:23:03 -05:00
return mDisplayClass ;
2009-11-24 19:40:29 -05:00
}
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
@Override
2011-02-06 17:09:48 -05:00
public FolderClass getSyncClass ( ) {
2012-12-07 09:50:55 -05:00
return ( FolderClass . INHERITED = = mSyncClass ) ? getDisplayClass ( ) : mSyncClass ;
2009-11-24 19:40:29 -05:00
}
2011-02-06 17:09:48 -05:00
public FolderClass getRawSyncClass ( ) {
2011-01-15 23:23:03 -05:00
return mSyncClass ;
2009-11-24 19:40:29 -05:00
}
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public FolderClass getPushClass ( ) {
2012-12-07 09:50:55 -05:00
return ( FolderClass . INHERITED = = mPushClass ) ? getSyncClass ( ) : mPushClass ;
2009-10-21 20:41:06 -04:00
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
public FolderClass getRawPushClass ( ) {
2011-01-15 23:23:03 -05:00
return mPushClass ;
2009-10-21 20:41:06 -04:00
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
public void setDisplayClass ( FolderClass displayClass ) throws MessagingException {
2011-01-15 23:23:03 -05:00
mDisplayClass = displayClass ;
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " display_class " , mDisplayClass . name ( ) ) ;
2011-01-15 23:23:03 -05:00
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
}
2011-02-06 17:09:48 -05:00
public void setSyncClass ( FolderClass syncClass ) throws MessagingException {
2011-01-15 23:23:03 -05:00
mSyncClass = syncClass ;
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " poll_class " , mSyncClass . name ( ) ) ;
2009-02-24 00:03:28 -05:00
}
2011-02-06 17:09:48 -05:00
public void setPushClass ( FolderClass pushClass ) throws MessagingException {
2011-01-15 23:23:03 -05:00
mPushClass = pushClass ;
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " push_class " , mPushClass . name ( ) ) ;
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
}
2010-04-29 00:59:14 -04:00
2011-02-06 17:09:48 -05:00
public boolean isIntegrate ( ) {
2010-04-05 22:54:48 -04:00
return mIntegrate ;
}
2011-02-06 17:09:48 -05:00
public void setIntegrate ( boolean integrate ) throws MessagingException {
2010-04-05 22:54:48 -04:00
mIntegrate = integrate ;
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " integrate " , mIntegrate ? 1 : 0 ) ;
2010-04-05 22:54:48 -04:00
}
2010-04-29 00:59:14 -04:00
2011-02-06 17:09:48 -05:00
private String getPrefId ( String name ) {
if ( prefId = = null ) {
2011-02-04 07:26:14 -05:00
prefId = uUid + " . " + name ;
}
return prefId ;
}
2011-02-06 17:09:48 -05:00
private String getPrefId ( ) throws MessagingException {
2011-02-04 07:26:14 -05:00
open ( OpenMode . READ_WRITE ) ;
return getPrefId ( mName ) ;
}
2011-02-06 17:09:48 -05:00
public void delete ( ) throws MessagingException {
2011-02-04 07:26:14 -05:00
String id = getPrefId ( ) ;
SharedPreferences . Editor editor = LocalStore . this . getPreferences ( ) . edit ( ) ;
editor . remove ( id + " .displayMode " ) ;
editor . remove ( id + " .syncMode " ) ;
editor . remove ( id + " .pushMode " ) ;
editor . remove ( id + " .inTopGroup " ) ;
editor . remove ( id + " .integrate " ) ;
editor . commit ( ) ;
}
2011-02-06 17:09:48 -05:00
public void save ( ) throws MessagingException {
2011-02-04 07:26:14 -05:00
SharedPreferences . Editor editor = LocalStore . this . getPreferences ( ) . edit ( ) ;
save ( editor ) ;
editor . commit ( ) ;
}
2011-02-06 17:09:48 -05:00
public void save ( SharedPreferences . Editor editor ) throws MessagingException {
2011-02-04 07:26:14 -05:00
String id = getPrefId ( ) ;
// there can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX
2011-04-05 05:27:39 -04:00
if ( mDisplayClass = = FolderClass . NO_CLASS & & ! mAccount . getInboxFolderName ( ) . equals ( getName ( ) ) ) {
2011-02-04 07:26:14 -05:00
editor . remove ( id + " .displayMode " ) ;
2011-02-06 17:09:48 -05:00
} else {
2011-02-04 07:26:14 -05:00
editor . putString ( id + " .displayMode " , mDisplayClass . name ( ) ) ;
}
2011-04-05 05:27:39 -04:00
if ( mSyncClass = = FolderClass . INHERITED & & ! mAccount . getInboxFolderName ( ) . equals ( getName ( ) ) ) {
2011-02-04 07:26:14 -05:00
editor . remove ( id + " .syncMode " ) ;
2011-02-06 17:09:48 -05:00
} else {
2011-02-04 07:26:14 -05:00
editor . putString ( id + " .syncMode " , mSyncClass . name ( ) ) ;
}
2011-04-05 05:27:39 -04:00
if ( mPushClass = = FolderClass . SECOND_CLASS & & ! mAccount . getInboxFolderName ( ) . equals ( getName ( ) ) ) {
2011-02-04 07:26:14 -05:00
editor . remove ( id + " .pushMode " ) ;
2011-02-06 17:09:48 -05:00
} else {
2011-02-04 07:26:14 -05:00
editor . putString ( id + " .pushMode " , mPushClass . name ( ) ) ;
}
editor . putBoolean ( id + " .inTopGroup " , mInTopGroup ) ;
editor . putBoolean ( id + " .integrate " , mIntegrate ) ;
}
2011-02-06 17:09:48 -05:00
public void refresh ( String name , PreferencesHolder prefHolder ) {
2011-02-04 07:26:14 -05:00
String id = getPrefId ( name ) ;
SharedPreferences preferences = LocalStore . this . getPreferences ( ) ;
2011-02-06 17:09:48 -05:00
try {
2011-02-04 07:26:14 -05:00
prefHolder . displayClass = FolderClass . valueOf ( preferences . getString ( id + " .displayMode " ,
2011-02-06 17:09:48 -05:00
prefHolder . displayClass . name ( ) ) ) ;
} catch ( Exception e ) {
2011-02-04 07:26:14 -05:00
Log . e ( K9 . LOG_TAG , " Unable to load displayMode for " + getName ( ) , e ) ;
}
2011-02-06 17:09:48 -05:00
if ( prefHolder . displayClass = = FolderClass . NONE ) {
2011-02-04 07:26:14 -05:00
prefHolder . displayClass = FolderClass . NO_CLASS ;
}
2011-02-06 17:09:48 -05:00
try {
2011-02-04 07:26:14 -05:00
prefHolder . syncClass = FolderClass . valueOf ( preferences . getString ( id + " .syncMode " ,
2011-02-06 17:09:48 -05:00
prefHolder . syncClass . name ( ) ) ) ;
} catch ( Exception e ) {
2011-02-04 07:26:14 -05:00
Log . e ( K9 . LOG_TAG , " Unable to load syncMode for " + getName ( ) , e ) ;
}
2011-02-06 17:09:48 -05:00
if ( prefHolder . syncClass = = FolderClass . NONE ) {
2011-02-04 07:26:14 -05:00
prefHolder . syncClass = FolderClass . INHERITED ;
}
2011-02-06 17:09:48 -05:00
try {
2011-02-04 07:26:14 -05:00
prefHolder . pushClass = FolderClass . valueOf ( preferences . getString ( id + " .pushMode " ,
2011-02-06 17:09:48 -05:00
prefHolder . pushClass . name ( ) ) ) ;
} catch ( Exception e ) {
2011-02-04 07:26:14 -05:00
Log . e ( K9 . LOG_TAG , " Unable to load pushMode for " + getName ( ) , e ) ;
}
2011-02-06 17:09:48 -05:00
if ( prefHolder . pushClass = = FolderClass . NONE ) {
2011-02-04 07:26:14 -05:00
prefHolder . pushClass = FolderClass . INHERITED ;
}
prefHolder . inTopGroup = preferences . getBoolean ( id + " .inTopGroup " , prefHolder . inTopGroup ) ;
prefHolder . integrate = preferences . getBoolean ( id + " .integrate " , prefHolder . integrate ) ;
}
2008-11-01 17:32:06 -04:00
@Override
2010-11-13 16:40:56 -05:00
public void fetch ( final Message [ ] messages , final FetchProfile fp , final MessageRetrievalListener listener )
2011-02-06 17:09:48 -05:00
throws MessagingException {
try {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
try {
2010-11-13 16:40:56 -05:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
if ( fp . contains ( FetchProfile . Item . BODY ) ) {
for ( Message message : messages ) {
2010-11-13 16:40:56 -05:00
LocalMessage localMessage = ( LocalMessage ) message ;
Cursor cursor = null ;
MimeMultipart mp = new MimeMultipart ( ) ;
mp . setSubType ( " mixed " ) ;
2011-02-06 17:09:48 -05:00
try {
2011-01-12 18:48:28 -05:00
cursor = db . rawQuery ( " SELECT html_content, text_content, mime_type FROM messages "
2010-11-13 16:40:56 -05:00
+ " WHERE id = ? " ,
new String [ ] { Long . toString ( localMessage . mId ) } ) ;
cursor . moveToNext ( ) ;
String htmlContent = cursor . getString ( 0 ) ;
String textContent = cursor . getString ( 1 ) ;
2011-01-12 18:48:28 -05:00
String mimeType = cursor . getString ( 2 ) ;
2011-10-27 11:17:43 -04:00
if ( mimeType ! = null & & mimeType . toLowerCase ( Locale . US ) . startsWith ( " multipart/ " ) ) {
2011-01-12 18:48:28 -05:00
// If this is a multipart message, preserve both text
// and html parts, as well as the subtype.
2011-10-27 11:17:43 -04:00
mp . setSubType ( mimeType . toLowerCase ( Locale . US ) . replaceFirst ( " ^multipart/ " , " " ) ) ;
2011-02-06 17:09:48 -05:00
if ( textContent ! = null ) {
2011-01-12 18:48:28 -05:00
LocalTextBody body = new LocalTextBody ( textContent , htmlContent ) ;
MimeBodyPart bp = new MimeBodyPart ( body , " text/plain " ) ;
mp . addBodyPart ( bp ) ;
}
2010-11-13 16:40:56 -05:00
2012-06-01 14:03:03 -04:00
if ( mAccount . getMessageFormat ( ) ! = MessageFormat . TEXT ) {
2012-04-08 12:29:08 -04:00
if ( htmlContent ! = null ) {
TextBody body = new TextBody ( htmlContent ) ;
MimeBodyPart bp = new MimeBodyPart ( body , " text/html " ) ;
mp . addBodyPart ( bp ) ;
}
2012-06-01 14:03:03 -04:00
2012-04-08 12:29:08 -04:00
// If we have both text and html content and our MIME type
// isn't multipart/alternative, then corral them into a new
// multipart/alternative part and put that into the parent.
// If it turns out that this is the only part in the parent
// MimeMultipart, it'll get fixed below before we attach to
// the message.
if ( textContent ! = null & & htmlContent ! = null & & ! mimeType . equalsIgnoreCase ( " multipart/alternative " ) ) {
MimeMultipart alternativeParts = mp ;
alternativeParts . setSubType ( " alternative " ) ;
mp = new MimeMultipart ( ) ;
mp . addBodyPart ( new MimeBodyPart ( alternativeParts ) ) ;
}
2011-01-12 18:48:28 -05:00
}
2011-02-06 17:09:48 -05:00
} else if ( mimeType ! = null & & mimeType . equalsIgnoreCase ( " text/plain " ) ) {
2011-01-12 18:48:28 -05:00
// If it's text, add only the plain part. The MIME
// container will drop away below.
2011-02-06 17:09:48 -05:00
if ( textContent ! = null ) {
2011-01-12 18:48:28 -05:00
LocalTextBody body = new LocalTextBody ( textContent , htmlContent ) ;
MimeBodyPart bp = new MimeBodyPart ( body , " text/plain " ) ;
mp . addBodyPart ( bp ) ;
}
2011-02-06 17:09:48 -05:00
} else if ( mimeType ! = null & & mimeType . equalsIgnoreCase ( " text/html " ) ) {
2011-01-12 18:48:28 -05:00
// If it's html, add only the html part. The MIME
// container will drop away below.
2011-02-06 17:09:48 -05:00
if ( htmlContent ! = null ) {
2011-01-12 18:48:28 -05:00
TextBody body = new TextBody ( htmlContent ) ;
MimeBodyPart bp = new MimeBodyPart ( body , " text/html " ) ;
mp . addBodyPart ( bp ) ;
}
2011-02-06 17:09:48 -05:00
} else {
2011-01-12 18:48:28 -05:00
// MIME type not set. Grab whatever part we can get,
// with Text taking precedence. This preserves pre-HTML
// composition behaviour.
2011-02-06 17:09:48 -05:00
if ( textContent ! = null ) {
2011-01-12 18:48:28 -05:00
LocalTextBody body = new LocalTextBody ( textContent , htmlContent ) ;
MimeBodyPart bp = new MimeBodyPart ( body , " text/plain " ) ;
mp . addBodyPart ( bp ) ;
2011-02-06 17:09:48 -05:00
} else if ( htmlContent ! = null ) {
2011-01-12 18:48:28 -05:00
TextBody body = new TextBody ( htmlContent ) ;
MimeBodyPart bp = new MimeBodyPart ( body , " text/html " ) ;
mp . addBodyPart ( bp ) ;
}
2010-11-13 16:40:56 -05:00
}
2011-01-12 18:48:28 -05:00
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2011-01-12 18:48:28 -05:00
Log . e ( K9 . LOG_TAG , " Exception fetching message: " , e ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . query (
" attachments " ,
2011-02-06 17:09:48 -05:00
new String [ ] {
2010-11-13 16:40:56 -05:00
" id " ,
" size " ,
" name " ,
" mime_type " ,
" store_data " ,
" content_uri " ,
" content_id " ,
" content_disposition "
} ,
" message_id = ? " ,
new String [ ] { Long . toString ( localMessage . mId ) } ,
null ,
null ,
null ) ;
2011-02-06 17:09:48 -05:00
while ( cursor . moveToNext ( ) ) {
2010-11-13 16:40:56 -05:00
long id = cursor . getLong ( 0 ) ;
int size = cursor . getInt ( 1 ) ;
String name = cursor . getString ( 2 ) ;
String type = cursor . getString ( 3 ) ;
String storeData = cursor . getString ( 4 ) ;
String contentUri = cursor . getString ( 5 ) ;
String contentId = cursor . getString ( 6 ) ;
String contentDisposition = cursor . getString ( 7 ) ;
Body body = null ;
2011-02-06 17:09:48 -05:00
if ( contentDisposition = = null ) {
2010-11-13 16:40:56 -05:00
contentDisposition = " attachment " ;
}
2011-02-06 17:09:48 -05:00
if ( contentUri ! = null ) {
2010-11-13 16:40:56 -05:00
body = new LocalAttachmentBody ( Uri . parse ( contentUri ) , mApplication ) ;
}
2011-04-06 11:18:14 -04:00
2010-11-13 16:40:56 -05:00
MimeBodyPart bp = new LocalAttachmentBodyPart ( body , id ) ;
bp . setHeader ( MimeHeader . HEADER_CONTENT_TRANSFER_ENCODING , " base64 " ) ;
2011-04-27 11:23:16 -04:00
if ( name ! = null ) {
bp . setHeader ( MimeHeader . HEADER_CONTENT_TYPE ,
String . format ( " %s; \ n name= \" %s \" " ,
type ,
2011-06-09 21:54:22 -04:00
name ) ) ;
2011-04-27 11:23:16 -04:00
bp . setHeader ( MimeHeader . HEADER_CONTENT_DISPOSITION ,
String . format ( " %s; \ n filename= \" %s \" ; \ n size=%d " ,
contentDisposition ,
2011-06-09 21:54:22 -04:00
name , // TODO: Should use encoded word defined in RFC 2231.
2011-04-27 11:23:16 -04:00
size ) ) ;
2012-02-27 17:20:30 -05:00
} else {
bp . setHeader ( MimeHeader . HEADER_CONTENT_TYPE , type ) ;
bp . setHeader ( MimeHeader . HEADER_CONTENT_DISPOSITION ,
String . format ( " %s; \ n size=%d " ,
contentDisposition ,
size ) ) ;
2011-04-27 11:23:16 -04:00
}
2010-11-13 16:40:56 -05:00
bp . setHeader ( MimeHeader . HEADER_CONTENT_ID , contentId ) ;
/ *
* HEADER_ANDROID_ATTACHMENT_STORE_DATA is a custom header we add to that
2011-01-12 18:48:28 -05:00
* we can later pull the attachment from the remote store if necessary .
2010-11-13 16:40:56 -05:00
* /
bp . setHeader ( MimeHeader . HEADER_ANDROID_ATTACHMENT_STORE_DATA , storeData ) ;
mp . addBodyPart ( bp ) ;
}
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
if ( mp . getCount ( ) = = 0 ) {
2011-01-12 18:48:28 -05:00
// If we have no body, remove the container and create a
// dummy plain text body. This check helps prevents us from
// triggering T_MIME_NO_TEXT and T_TVD_MIME_NO_HEADERS
// SpamAssassin rules.
localMessage . setHeader ( MimeHeader . HEADER_CONTENT_TYPE , " text/plain " ) ;
localMessage . setBody ( new TextBody ( " " ) ) ;
2011-02-06 17:09:48 -05:00
} else if ( mp . getCount ( ) = = 1 & & ( mp . getBodyPart ( 0 ) instanceof LocalAttachmentBodyPart ) = = false )
2011-01-30 20:54:27 -05:00
2010-11-13 16:40:56 -05:00
{
2011-01-12 18:48:28 -05:00
// If we have only one part, drop the MimeMultipart container.
2010-11-13 16:40:56 -05:00
BodyPart part = mp . getBodyPart ( 0 ) ;
localMessage . setHeader ( MimeHeader . HEADER_CONTENT_TYPE , part . getContentType ( ) ) ;
localMessage . setBody ( part . getBody ( ) ) ;
2011-02-06 17:09:48 -05:00
} else {
2011-01-12 18:48:28 -05:00
// Otherwise, attach the MimeMultipart to the message.
2010-11-13 16:40:56 -05:00
localMessage . setBody ( mp ) ;
}
}
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
2008-11-01 17:32:06 -04:00
}
2010-11-13 16:40:56 -05:00
return null ;
2008-11-01 17:32:06 -04:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
}
@Override
2010-05-30 17:20:47 -04:00
public Message [ ] getMessages ( int start , int end , Date earliestDate , MessageRetrievalListener listener )
2011-02-06 17:09:48 -05:00
throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
throw new MessagingException (
2009-11-24 19:40:29 -05:00
" LocalStore.getMessages(int, int, MessageRetrievalListener) not yet implemented " ) ;
2008-11-01 17:32:06 -04:00
}
2010-05-21 11:34:29 -04:00
/ * *
* Populate the header fields of the given list of messages by reading
* the saved header data from the database .
2010-05-30 00:17:00 -04:00
*
2010-05-21 11:34:29 -04:00
* @param messages
* The messages whose headers should be loaded .
2010-11-13 16:40:56 -05:00
* @throws UnavailableStorageException
2010-05-21 11:34:29 -04:00
* /
2011-02-06 17:09:48 -05:00
private void populateHeaders ( final List < LocalMessage > messages ) throws UnavailableStorageException {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
2010-11-13 16:40:56 -05:00
Cursor cursor = null ;
2011-10-06 12:28:14 -04:00
if ( messages . isEmpty ( ) ) {
2010-11-13 16:40:56 -05:00
return null ;
2009-06-08 23:11:35 -04:00
}
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
Map < Long , LocalMessage > popMessages = new HashMap < Long , LocalMessage > ( ) ;
List < String > ids = new ArrayList < String > ( ) ;
2011-09-30 02:18:00 -04:00
StringBuilder questions = new StringBuilder ( ) ;
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
for ( int i = 0 ; i < messages . size ( ) ; i + + ) {
if ( i ! = 0 ) {
2010-11-13 16:40:56 -05:00
questions . append ( " , " ) ;
}
questions . append ( " ? " ) ;
LocalMessage message = messages . get ( i ) ;
Long id = message . getId ( ) ;
ids . add ( Long . toString ( id ) ) ;
popMessages . put ( id , message ) ;
2009-11-24 19:40:29 -05:00
2010-11-13 16:40:56 -05:00
}
2009-11-24 19:40:29 -05:00
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery (
2011-11-14 23:32:46 -05:00
" SELECT message_id, name, value FROM headers " + " WHERE message_id in ( " + questions + " ) ORDER BY id ASC " ,
2010-11-13 16:40:56 -05:00
ids . toArray ( EMPTY_STRING_ARRAY ) ) ;
2009-11-24 19:40:29 -05:00
2010-11-13 16:40:56 -05:00
2011-02-06 17:09:48 -05:00
while ( cursor . moveToNext ( ) ) {
2010-11-13 16:40:56 -05:00
Long id = cursor . getLong ( 0 ) ;
String name = cursor . getString ( 1 ) ;
String value = cursor . getString ( 2 ) ;
//Log.i(K9.LOG_TAG, "Retrieved header name= " + name + ", value = " + value + " for message " + id);
popMessages . get ( id ) . addHeader ( name , value ) ;
}
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
return null ;
2009-06-08 23:11:35 -04:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2009-06-08 23:11:35 -04:00
}
2012-01-21 23:14:58 -05:00
public String getMessageUidById ( final long id ) throws MessagingException {
try {
return database . execute ( false , new DbCallback < String > ( ) {
@Override
public String doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
open ( OpenMode . READ_WRITE ) ;
Cursor cursor = null ;
try {
cursor = db . rawQuery (
" SELECT uid FROM messages " +
" WHERE id = ? AND folder_id = ? " ,
new String [ ] {
Long . toString ( id ) , Long . toString ( mFolderId )
} ) ;
if ( ! cursor . moveToNext ( ) ) {
return null ;
}
return cursor . getString ( 0 ) ;
} finally {
Utility . closeQuietly ( cursor ) ;
}
} catch ( MessagingException e ) {
throw new WrappedException ( e ) ;
}
}
} ) ;
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
}
}
2008-11-01 17:32:06 -04:00
@Override
2012-03-05 15:04:34 -05:00
public LocalMessage getMessage ( final String uid ) throws MessagingException {
2011-02-06 17:09:48 -05:00
try {
2012-03-05 15:04:34 -05:00
return database . execute ( false , new DbCallback < LocalMessage > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2012-03-05 15:04:34 -05:00
public LocalMessage doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
open ( OpenMode . READ_WRITE ) ;
LocalMessage message = new LocalMessage ( uid , LocalFolder . this ) ;
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
cursor = db . rawQuery (
2013-01-10 21:40:35 -05:00
" SELECT " +
GET_MESSAGES_COLS +
" FROM messages " +
2013-01-11 22:21:53 -05:00
" LEFT JOIN threads ON (threads.message_id = messages.id) " +
2013-01-10 21:40:35 -05:00
" WHERE uid = ? AND folder_id = ? " ,
2011-02-06 17:09:48 -05:00
new String [ ] {
2010-11-13 16:40:56 -05:00
message . getUid ( ) , Long . toString ( mFolderId )
} ) ;
2012-10-23 18:32:29 -04:00
if ( ! cursor . moveToNext ( ) ) {
2010-11-13 16:40:56 -05:00
return null ;
}
message . populateFromGetMessageCursor ( cursor ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
return message ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
}
@Override
2011-02-06 17:09:48 -05:00
public Message [ ] getMessages ( MessageRetrievalListener listener ) throws MessagingException {
2009-11-24 19:40:29 -05:00
return getMessages ( listener , true ) ;
}
2009-11-17 11:54:50 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Message [ ] getMessages ( final MessageRetrievalListener listener , final boolean includeDeleted ) throws MessagingException {
try {
return database . execute ( false , new DbCallback < Message [ ] > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Message [ ] doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
2010-11-13 16:40:56 -05:00
open ( OpenMode . READ_WRITE ) ;
return LocalStore . this . getMessages (
listener ,
LocalFolder . this ,
2013-01-10 21:40:35 -05:00
" SELECT " + GET_MESSAGES_COLS +
" FROM messages " +
2013-01-11 22:21:53 -05:00
" LEFT JOIN threads ON (threads.message_id = messages.id) " +
2013-01-10 21:40:35 -05:00
" WHERE (empty IS NULL OR empty != 1) AND " +
( includeDeleted ? " " : " deleted = 0 AND " ) +
" folder_id = ? ORDER BY date DESC " ,
new String [ ] { Long . toString ( mFolderId ) }
2010-11-13 16:40:56 -05:00
) ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2010-11-13 16:40:56 -05:00
}
2009-12-27 11:50:21 -05:00
}
2008-11-01 17:32:06 -04:00
@Override
public Message [ ] getMessages ( String [ ] uids , MessageRetrievalListener listener )
2011-02-06 17:09:48 -05:00
throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
if ( uids = = null ) {
2008-11-01 17:32:06 -04:00
return getMessages ( listener ) ;
}
ArrayList < Message > messages = new ArrayList < Message > ( ) ;
2011-02-06 17:09:48 -05:00
for ( String uid : uids ) {
2010-03-03 23:00:30 -05:00
Message message = getMessage ( uid ) ;
2011-02-06 17:09:48 -05:00
if ( message ! = null ) {
2010-03-03 23:00:30 -05:00
messages . add ( message ) ;
}
2008-11-01 17:32:06 -04:00
}
2010-08-07 11:10:07 -04:00
return messages . toArray ( EMPTY_MESSAGE_ARRAY ) ;
2008-11-01 17:32:06 -04:00
}
@Override
2011-06-28 07:20:48 -04:00
public Map < String , String > copyMessages ( Message [ ] msgs , Folder folder ) throws MessagingException {
2011-02-06 17:09:48 -05:00
if ( ! ( folder instanceof LocalFolder ) ) {
2008-11-01 17:32:06 -04:00
throw new MessagingException ( " copyMessages called with incorrect Folder " ) ;
}
2011-06-28 07:20:48 -04:00
return ( ( LocalFolder ) folder ) . appendMessages ( msgs , true ) ;
2008-11-01 17:32:06 -04:00
}
2009-11-24 19:40:29 -05:00
2009-03-05 02:32:45 -05:00
@Override
2011-06-28 07:20:48 -04:00
public Map < String , String > moveMessages ( final Message [ ] msgs , final Folder destFolder ) throws MessagingException {
2011-02-06 17:09:48 -05:00
if ( ! ( destFolder instanceof LocalFolder ) ) {
2010-01-09 14:49:54 -05:00
throw new MessagingException ( " moveMessages called with non-LocalFolder " ) ;
2009-03-05 02:32:45 -05:00
}
2009-03-07 02:20:15 -05:00
2010-11-13 16:40:56 -05:00
final LocalFolder lDestFolder = ( LocalFolder ) destFolder ;
2010-04-29 00:59:14 -04:00
2011-06-28 07:20:48 -04:00
final Map < String , String > uidMap = new HashMap < String , String > ( ) ;
2011-02-06 17:09:48 -05:00
try {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
2010-11-13 16:40:56 -05:00
lDestFolder . open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
for ( Message message : msgs ) {
2010-11-13 16:40:56 -05:00
LocalMessage lMessage = ( LocalMessage ) message ;
2010-01-13 20:07:28 -05:00
2010-11-13 16:40:56 -05:00
String oldUID = message . getUid ( ) ;
2009-11-24 19:40:29 -05:00
2012-10-08 16:51:29 -04:00
if ( K9 . DEBUG ) {
2010-11-13 16:40:56 -05:00
Log . d ( K9 . LOG_TAG , " Updating folder_id to " + lDestFolder . getId ( ) + " for message with UID "
+ message . getUid ( ) + " , id " + lMessage . getId ( ) + " currently in folder " + getName ( ) ) ;
2012-10-08 16:51:29 -04:00
}
2010-11-13 16:40:56 -05:00
2012-02-16 17:37:44 -05:00
String newUid = K9 . LOCAL_UID_PREFIX + UUID . randomUUID ( ) . toString ( ) ;
message . setUid ( newUid ) ;
uidMap . put ( oldUID , newUid ) ;
2010-11-13 16:40:56 -05:00
2012-10-08 16:51:29 -04:00
// Message threading in the target folder
ThreadInfo threadInfo = lDestFolder . doMessageThreading ( db , message ) ;
/ *
2013-01-10 21:40:35 -05:00
* " Move " the message into the new folder
2012-10-08 16:51:29 -04:00
* /
2013-01-10 21:40:35 -05:00
long msgId = lMessage . getId ( ) ;
String [ ] idArg = new String [ ] { Long . toString ( msgId ) } ;
2012-10-08 16:51:29 -04:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " folder_id " , lDestFolder . getId ( ) ) ;
cv . put ( " uid " , newUid ) ;
db . update ( " messages " , cv , " id = ? " , idArg ) ;
2013-01-10 21:40:35 -05:00
// Create/update entry in 'threads' table for the message in the
// target folder
cv . clear ( ) ;
cv . put ( " message_id " , msgId ) ;
if ( threadInfo . threadId = = - 1 ) {
if ( threadInfo . rootId ! = - 1 ) {
cv . put ( " root " , threadInfo . rootId ) ;
}
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
if ( threadInfo . parentId ! = - 1 ) {
cv . put ( " parent " , threadInfo . parentId ) ;
}
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
db . insert ( " threads " , null , cv ) ;
} else {
db . update ( " threads " , cv , " id = ? " ,
new String [ ] { Long . toString ( threadInfo . threadId ) } ) ;
2012-10-08 16:51:29 -04:00
}
2010-11-13 16:40:56 -05:00
2012-02-16 17:37:44 -05:00
/ *
* Add a placeholder message so we won ' t download the original
* message again if we synchronize before the remote move is
* complete .
* /
2012-10-08 16:51:29 -04:00
// We need to open this folder to get the folder id
open ( OpenMode . READ_WRITE ) ;
cv . clear ( ) ;
cv . put ( " uid " , oldUID ) ;
2012-12-03 23:13:58 -05:00
cv . putNull ( " flags " ) ;
cv . put ( " read " , 1 ) ;
2012-10-08 16:51:29 -04:00
cv . put ( " deleted " , 1 ) ;
cv . put ( " folder_id " , mFolderId ) ;
2013-01-10 21:40:35 -05:00
cv . put ( " empty " , 0 ) ;
2012-10-08 16:51:29 -04:00
String messageId = message . getMessageId ( ) ;
if ( messageId ! = null ) {
cv . put ( " message_id " , messageId ) ;
}
final long newId ;
2013-01-10 21:40:35 -05:00
if ( threadInfo . msgId ! = - 1 ) {
2012-10-08 16:51:29 -04:00
// There already existed an empty message in the target folder.
// Let's use it as placeholder.
2013-01-10 21:40:35 -05:00
newId = threadInfo . msgId ;
2012-10-08 16:51:29 -04:00
db . update ( " messages " , cv , " id = ? " ,
new String [ ] { Long . toString ( newId ) } ) ;
} else {
newId = db . insert ( " messages " , null , cv ) ;
}
/ *
2013-01-10 21:40:35 -05:00
* Update old entry in ' threads ' table to point to the newly
* created placeholder .
2012-10-08 16:51:29 -04:00
* /
cv . clear ( ) ;
2013-01-10 21:40:35 -05:00
cv . put ( " message_id " , newId ) ;
db . update ( " threads " , cv , " id = ? " ,
new String [ ] { Long . toString ( lMessage . getThreadId ( ) ) } ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
return null ;
}
} ) ;
2012-10-23 18:08:44 -04:00
notifyChange ( ) ;
2011-06-28 07:20:48 -04:00
return uidMap ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2009-11-24 19:40:29 -05:00
}
2009-03-05 02:32:45 -05:00
}
2008-11-01 17:32:06 -04:00
2010-12-18 05:12:52 -05:00
/ * *
* Convenience transaction wrapper for storing a message and set it as fully downloaded . Implemented mainly to speed up DB transaction commit .
2010-12-24 13:55:05 -05:00
*
2010-12-18 05:12:52 -05:00
* @param message Message to store . Never < code > null < / code > .
* @param runnable What to do before setting { @link Flag # X_DOWNLOADED_FULL } . Never < code > null < / code > .
* @return The local version of the message . Never < code > null < / code > .
* @throws MessagingException
* /
2011-02-06 17:09:48 -05:00
public Message storeSmallMessage ( final Message message , final Runnable runnable ) throws MessagingException {
return database . execute ( true , new DbCallback < Message > ( ) {
2010-12-18 05:12:52 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Message doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
2010-12-18 05:12:52 -05:00
appendMessages ( new Message [ ] { message } ) ;
final String uid = message . getUid ( ) ;
final Message result = getMessage ( uid ) ;
runnable . run ( ) ;
// Set a flag indicating this message has now be fully downloaded
result . setFlag ( Flag . X_DOWNLOADED_FULL , true ) ;
return result ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-12-18 05:12:52 -05:00
throw new WrappedException ( e ) ;
}
}
} ) ;
}
2008-11-01 17:32:06 -04:00
/ * *
* The method differs slightly from the contract ; If an incoming message already has a uid
* assigned and it matches the uid of an existing message then this message will replace the
* old message . It is implemented as a delete / insert . This functionality is used in saving
* of drafts and re - synchronization of updated server messages .
2010-05-21 11:34:29 -04:00
*
* NOTE that although this method is located in the LocalStore class , it is not guaranteed
* that the messages supplied as parameters are actually { @link LocalMessage } instances ( in
* fact , in most cases , they are not ) . Therefore , if you want to make local changes only to a
* message , retrieve the appropriate local message instance first ( if it already exists ) .
2008-11-01 17:32:06 -04:00
* /
@Override
2011-06-28 07:20:48 -04:00
public Map < String , String > appendMessages ( Message [ ] messages ) throws MessagingException {
return appendMessages ( messages , false ) ;
2008-11-01 17:32:06 -04:00
}
2012-12-07 09:50:55 -05:00
public void destroyMessages ( final Message [ ] messages ) {
2011-02-13 19:47:30 -05:00
try {
database . execute ( true , new DbCallback < Void > ( ) {
@Override
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
for ( Message message : messages ) {
try {
message . destroy ( ) ;
} catch ( MessagingException e ) {
throw new WrappedException ( e ) ;
2011-02-09 07:27:24 -05:00
}
}
2011-02-13 19:47:30 -05:00
return null ;
}
} ) ;
} catch ( MessagingException e ) {
throw new WrappedException ( e ) ;
}
2011-02-09 07:27:24 -05:00
}
2012-10-08 16:51:29 -04:00
private ThreadInfo getThreadInfo ( SQLiteDatabase db , String messageId ) {
2013-01-10 21:40:35 -05:00
String sql = " SELECT t.id, t.message_id, t.root, t.parent " +
2013-01-11 22:21:53 -05:00
" FROM messages m " +
" LEFT JOIN threads t ON (t.message_id = m.id) " +
2013-01-10 21:40:35 -05:00
" WHERE m.folder_id = ? AND m.message_id = ? " ;
String [ ] selectionArgs = { Long . toString ( mFolderId ) , messageId } ;
Cursor cursor = db . rawQuery ( sql , selectionArgs ) ;
2012-10-08 16:51:29 -04:00
if ( cursor ! = null ) {
try {
if ( cursor . getCount ( ) = = 1 ) {
cursor . moveToFirst ( ) ;
2013-01-10 21:40:35 -05:00
long threadId = cursor . getLong ( 0 ) ;
long msgId = cursor . getLong ( 1 ) ;
long rootId = ( cursor . isNull ( 2 ) ) ? - 1 : cursor . getLong ( 2 ) ;
long parentId = ( cursor . isNull ( 3 ) ) ? - 1 : cursor . getLong ( 3 ) ;
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
return new ThreadInfo ( threadId , msgId , messageId , rootId , parentId ) ;
2012-10-08 16:51:29 -04:00
}
} finally {
cursor . close ( ) ;
}
}
return null ;
}
2011-02-09 07:27:24 -05:00
2008-11-01 17:32:06 -04:00
/ * *
* The method differs slightly from the contract ; If an incoming message already has a uid
2012-01-21 23:04:15 -05:00
* assigned and it matches the uid of an existing message then this message will replace
* the old message . This functionality is used in saving of drafts and re - synchronization
* of updated server messages .
2010-05-21 11:34:29 -04:00
*
* NOTE that although this method is located in the LocalStore class , it is not guaranteed
* that the messages supplied as parameters are actually { @link LocalMessage } instances ( in
* fact , in most cases , they are not ) . Therefore , if you want to make local changes only to a
* message , retrieve the appropriate local message instance first ( if it already exists ) .
2010-12-28 04:10:50 -05:00
* @param messages
* @param copy
2011-06-28 07:20:48 -04:00
* @return Map < String , String > uidMap of srcUids - > destUids
2008-11-01 17:32:06 -04:00
* /
2011-06-28 07:20:48 -04:00
private Map < String , String > appendMessages ( final Message [ ] messages , final boolean copy ) throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
try {
2011-06-28 07:20:48 -04:00
final Map < String , String > uidMap = new HashMap < String , String > ( ) ;
2011-02-06 17:09:48 -05:00
database . execute ( true , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
for ( Message message : messages ) {
if ( ! ( message instanceof MimeMessage ) ) {
2010-11-13 16:40:56 -05:00
throw new Error ( " LocalStore can only store Messages that extend MimeMessage " ) ;
}
2012-01-21 23:04:15 -05:00
long oldMessageId = - 1 ;
2010-11-13 16:40:56 -05:00
String uid = message . getUid ( ) ;
2011-02-06 17:09:48 -05:00
if ( uid = = null | | copy ) {
2012-02-16 17:52:44 -05:00
/ *
* Create a new message in the database
* /
String randomLocalUid = K9 . LOCAL_UID_PREFIX +
UUID . randomUUID ( ) . toString ( ) ;
if ( copy ) {
// Save mapping: source UID -> target UID
2011-07-24 21:20:26 -04:00
uidMap . put ( uid , randomLocalUid ) ;
2012-02-16 17:52:44 -05:00
} else {
// Modify the Message instance to reference the new UID
message . setUid ( randomLocalUid ) ;
2010-11-13 16:40:56 -05:00
}
2012-02-16 17:52:44 -05:00
// The message will be saved with the newly generated UID
2011-07-24 21:20:26 -04:00
uid = randomLocalUid ;
2011-02-06 17:09:48 -05:00
} else {
2012-02-16 17:52:44 -05:00
/ *
* Replace an existing message in the database
* /
2012-03-05 15:04:34 -05:00
LocalMessage oldMessage = getMessage ( uid ) ;
2012-01-21 23:04:15 -05:00
if ( oldMessage ! = null ) {
oldMessageId = oldMessage . getId ( ) ;
2010-11-13 16:40:56 -05:00
}
2012-01-21 23:04:15 -05:00
2010-11-13 16:40:56 -05:00
deleteAttachments ( message . getUid ( ) ) ;
}
2012-10-08 16:51:29 -04:00
long rootId = - 1 ;
long parentId = - 1 ;
if ( oldMessageId = = - 1 ) {
// This is a new message. Do the message threading.
ThreadInfo threadInfo = doMessageThreading ( db , message ) ;
2013-01-10 21:40:35 -05:00
oldMessageId = threadInfo . msgId ;
2012-10-08 16:51:29 -04:00
rootId = threadInfo . rootId ;
parentId = threadInfo . parentId ;
}
2012-03-15 16:21:00 -04:00
boolean isDraft = ( message . getHeader ( K9 . IDENTITY_HEADER ) ! = null ) ;
List < Part > attachments ;
String text ;
String html ;
if ( isDraft ) {
// Don't modify the text/plain or text/html part of our own
// draft messages because this will cause the values stored in
// the identity header to be wrong.
ViewableContainer container =
MimeUtility . extractPartsFromDraft ( message ) ;
text = container . text ;
html = container . html ;
attachments = container . attachments ;
} else {
ViewableContainer container =
MimeUtility . extractTextAndAttachments ( mApplication , message ) ;
2010-11-13 16:40:56 -05:00
2012-03-15 16:21:00 -04:00
attachments = container . attachments ;
text = container . text ;
html = HtmlConverter . convertEmoji2Img ( container . html ) ;
}
2011-05-15 18:36:46 -04:00
2013-01-05 07:20:46 -05:00
String preview = Message . calculateContentPreview ( text ) ;
2010-11-13 16:40:56 -05:00
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " uid " , uid ) ;
cv . put ( " subject " , message . getSubject ( ) ) ;
cv . put ( " sender_list " , Address . pack ( message . getFrom ( ) ) ) ;
cv . put ( " date " , message . getSentDate ( ) = = null
? System . currentTimeMillis ( ) : message . getSentDate ( ) . getTime ( ) ) ;
2012-10-08 16:51:29 -04:00
cv . put ( " flags " , serializeFlags ( message . getFlags ( ) ) ) ;
2010-11-13 16:40:56 -05:00
cv . put ( " deleted " , message . isSet ( Flag . DELETED ) ? 1 : 0 ) ;
2012-12-03 23:13:58 -05:00
cv . put ( " read " , message . isSet ( Flag . SEEN ) ? 1 : 0 ) ;
cv . put ( " flagged " , message . isSet ( Flag . FLAGGED ) ? 1 : 0 ) ;
cv . put ( " answered " , message . isSet ( Flag . ANSWERED ) ? 1 : 0 ) ;
cv . put ( " forwarded " , message . isSet ( Flag . FORWARDED ) ? 1 : 0 ) ;
2010-11-13 16:40:56 -05:00
cv . put ( " folder_id " , mFolderId ) ;
cv . put ( " to_list " , Address . pack ( message . getRecipients ( RecipientType . TO ) ) ) ;
cv . put ( " cc_list " , Address . pack ( message . getRecipients ( RecipientType . CC ) ) ) ;
cv . put ( " bcc_list " , Address . pack ( message . getRecipients ( RecipientType . BCC ) ) ) ;
cv . put ( " html_content " , html . length ( ) > 0 ? html : null ) ;
cv . put ( " text_content " , text . length ( ) > 0 ? text : null ) ;
cv . put ( " preview " , preview . length ( ) > 0 ? preview : null ) ;
cv . put ( " reply_to_list " , Address . pack ( message . getReplyTo ( ) ) ) ;
cv . put ( " attachment_count " , attachments . size ( ) ) ;
cv . put ( " internal_date " , message . getInternalDate ( ) = = null
? System . currentTimeMillis ( ) : message . getInternalDate ( ) . getTime ( ) ) ;
2011-01-12 18:48:28 -05:00
cv . put ( " mime_type " , message . getMimeType ( ) ) ;
2012-10-08 16:51:29 -04:00
cv . put ( " empty " , 0 ) ;
2010-11-13 16:40:56 -05:00
String messageId = message . getMessageId ( ) ;
2011-02-06 17:09:48 -05:00
if ( messageId ! = null ) {
2010-11-13 16:40:56 -05:00
cv . put ( " message_id " , messageId ) ;
}
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
long msgId ;
2012-01-21 23:04:15 -05:00
if ( oldMessageId = = - 1 ) {
2013-01-10 21:40:35 -05:00
msgId = db . insert ( " messages " , " uid " , cv ) ;
// Create entry in 'threads' table
cv . clear ( ) ;
cv . put ( " message_id " , msgId ) ;
if ( rootId ! = - 1 ) {
cv . put ( " root " , rootId ) ;
}
if ( parentId ! = - 1 ) {
cv . put ( " parent " , parentId ) ;
}
db . insert ( " threads " , null , cv ) ;
2012-01-21 23:04:15 -05:00
} else {
db . update ( " messages " , cv , " id = ? " , new String [ ] { Long . toString ( oldMessageId ) } ) ;
2013-01-10 21:40:35 -05:00
msgId = oldMessageId ;
2012-01-21 23:04:15 -05:00
}
2013-01-10 21:40:35 -05:00
2011-02-06 17:09:48 -05:00
for ( Part attachment : attachments ) {
2013-01-10 21:40:35 -05:00
saveAttachment ( msgId , attachment , copy ) ;
2010-11-13 16:40:56 -05:00
}
2013-01-10 21:40:35 -05:00
saveHeaders ( msgId , ( MimeMessage ) message ) ;
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2010-11-13 16:40:56 -05:00
throw new MessagingException ( " Error appending message " , e ) ;
}
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
return null ;
2010-04-16 10:33:54 -04:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2012-10-23 18:08:44 -04:00
notifyChange ( ) ;
2011-06-28 07:20:48 -04:00
return uidMap ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
}
/ * *
* Update the given message in the LocalStore without first deleting the existing
* message ( contrast with appendMessages ) . This method is used to store changes
* to the given message while updating attachments and not removing existing
* attachment data .
* TODO In the future this method should be combined with appendMessages since the Message
* contains enough data to decide what to do .
* @param message
* @throws MessagingException
* /
2011-02-06 17:09:48 -05:00
public void updateMessage ( final LocalMessage message ) throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
try {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
2010-11-13 16:40:56 -05:00
message . buildMimeRepresentation ( ) ;
2009-05-03 16:52:32 -04:00
2012-02-13 17:11:59 -05:00
ViewableContainer container =
MimeUtility . extractTextAndAttachments ( mApplication , message ) ;
2010-11-13 16:40:56 -05:00
2012-02-13 17:11:59 -05:00
List < Part > attachments = container . attachments ;
String text = container . text ;
2012-02-17 18:44:24 -05:00
String html = HtmlConverter . convertEmoji2Img ( container . html ) ;
2011-05-15 18:36:46 -04:00
2013-01-05 07:20:46 -05:00
String preview = Message . calculateContentPreview ( text ) ;
2012-02-13 17:11:59 -05:00
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
db . execSQL ( " UPDATE messages SET "
+ " uid = ?, subject = ?, sender_list = ?, date = ?, flags = ?, "
+ " folder_id = ?, to_list = ?, cc_list = ?, bcc_list = ?, "
+ " html_content = ?, text_content = ?, preview = ?, reply_to_list = ?, "
2012-12-03 23:13:58 -05:00
+ " attachment_count = ?, read = ?, flagged = ?, answered = ?, forwarded = ? "
+ " WHERE id = ? " ,
2011-02-06 17:09:48 -05:00
new Object [ ] {
2010-11-13 16:40:56 -05:00
message . getUid ( ) ,
message . getSubject ( ) ,
Address . pack ( message . getFrom ( ) ) ,
message . getSentDate ( ) = = null ? System
. currentTimeMillis ( ) : message . getSentDate ( )
. getTime ( ) ,
2012-10-08 16:51:29 -04:00
serializeFlags ( message . getFlags ( ) ) ,
2010-11-13 16:40:56 -05:00
mFolderId ,
Address . pack ( message
. getRecipients ( RecipientType . TO ) ) ,
Address . pack ( message
. getRecipients ( RecipientType . CC ) ) ,
Address . pack ( message
. getRecipients ( RecipientType . BCC ) ) ,
html . length ( ) > 0 ? html : null ,
text . length ( ) > 0 ? text : null ,
preview . length ( ) > 0 ? preview : null ,
Address . pack ( message . getReplyTo ( ) ) ,
attachments . size ( ) ,
2012-12-03 23:13:58 -05:00
message . isSet ( Flag . SEEN ) ? 1 : 0 ,
message . isSet ( Flag . FLAGGED ) ? 1 : 0 ,
message . isSet ( Flag . ANSWERED ) ? 1 : 0 ,
message . isSet ( Flag . FORWARDED ) ? 1 : 0 ,
2010-11-13 16:40:56 -05:00
message . mId
} ) ;
2011-02-06 17:09:48 -05:00
for ( int i = 0 , count = attachments . size ( ) ; i < count ; i + + ) {
2010-11-13 16:40:56 -05:00
Part attachment = attachments . get ( i ) ;
saveAttachment ( message . mId , attachment , false ) ;
}
saveHeaders ( message . getId ( ) , message ) ;
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2010-11-13 16:40:56 -05:00
throw new MessagingException ( " Error appending message " , e ) ;
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
2012-10-23 18:08:44 -04:00
notifyChange ( ) ;
2008-11-01 17:32:06 -04:00
}
2010-05-21 11:34:29 -04:00
/ * *
* Save the headers of the given message . Note that the message is not
* necessarily a { @link LocalMessage } instance .
2010-12-28 04:10:50 -05:00
* @param id
* @param message
* @throws com . fsck . k9 . mail . MessagingException
2010-05-21 11:34:29 -04:00
* /
2011-02-06 17:09:48 -05:00
private void saveHeaders ( final long id , final MimeMessage message ) throws MessagingException {
database . execute ( true , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
2010-11-13 16:40:56 -05:00
deleteHeaders ( id ) ;
2011-02-06 17:09:48 -05:00
for ( String name : message . getHeaderNames ( ) ) {
2010-11-13 16:40:56 -05:00
String [ ] values = message . getHeader ( name ) ;
2011-02-06 17:09:48 -05:00
for ( String value : values ) {
2010-11-13 16:40:56 -05:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " message_id " , id ) ;
cv . put ( " name " , name ) ;
cv . put ( " value " , value ) ;
db . insert ( " headers " , " name " , cv ) ;
}
2009-06-08 23:11:35 -04:00
}
2010-05-21 11:34:29 -04:00
2012-09-10 12:05:52 -04:00
// Remember that all headers for this message have been saved, so it is
// not necessary to download them again in case the user wants to see all headers.
List < Flag > appendedFlags = new ArrayList < Flag > ( ) ;
appendedFlags . addAll ( Arrays . asList ( message . getFlags ( ) ) ) ;
appendedFlags . add ( Flag . X_GOT_ALL_HEADERS ) ;
2010-11-13 16:40:56 -05:00
2012-09-10 12:05:52 -04:00
db . execSQL ( " UPDATE messages " + " SET flags = ? " + " WHERE id = ? " ,
new Object [ ]
2012-10-08 16:51:29 -04:00
{ serializeFlags ( appendedFlags . toArray ( EMPTY_FLAG_ARRAY ) ) , id } ) ;
2010-11-13 16:40:56 -05:00
return null ;
}
} ) ;
2009-06-08 23:11:35 -04:00
}
2009-11-24 19:40:29 -05:00
2011-02-06 17:09:48 -05:00
private void deleteHeaders ( final long id ) throws UnavailableStorageException {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
2010-11-13 16:40:56 -05:00
db . execSQL ( " DELETE FROM headers WHERE message_id = ? " , new Object [ ]
{ id } ) ;
return null ;
}
} ) ;
2009-06-08 23:11:35 -04:00
}
2008-11-01 17:32:06 -04:00
/ * *
* @param messageId
* @param attachment
2010-12-28 04:10:50 -05:00
* @param saveAsNew
2008-11-01 17:32:06 -04:00
* @throws IOException
* @throws MessagingException
* /
2010-11-13 16:40:56 -05:00
private void saveAttachment ( final long messageId , final Part attachment , final boolean saveAsNew )
2011-02-06 17:09:48 -05:00
throws IOException , MessagingException {
try {
database . execute ( true , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
2010-11-13 16:40:56 -05:00
long attachmentId = - 1 ;
Uri contentUri = null ;
int size = - 1 ;
File tempAttachmentFile = null ;
2008-11-01 17:32:06 -04:00
2011-02-06 17:09:48 -05:00
if ( ( ! saveAsNew ) & & ( attachment instanceof LocalAttachmentBodyPart ) ) {
2010-11-13 16:40:56 -05:00
attachmentId = ( ( LocalAttachmentBodyPart ) attachment ) . getAttachmentId ( ) ;
}
2008-11-01 17:32:06 -04:00
2010-12-18 17:56:40 -05:00
final File attachmentDirectory = StorageManager . getInstance ( mApplication ) . getAttachmentDirectory ( uUid , database . getStorageProviderId ( ) ) ;
2011-02-06 17:09:48 -05:00
if ( attachment . getBody ( ) ! = null ) {
2010-11-13 16:40:56 -05:00
Body body = attachment . getBody ( ) ;
2011-02-06 17:09:48 -05:00
if ( body instanceof LocalAttachmentBody ) {
2010-11-13 16:40:56 -05:00
contentUri = ( ( LocalAttachmentBody ) body ) . getContentUri ( ) ;
2012-02-13 17:11:59 -05:00
} else if ( body instanceof Message ) {
// It's a message, so use Message.writeTo() to output the
// message including all children.
Message message = ( Message ) body ;
tempAttachmentFile = File . createTempFile ( " att " , null , attachmentDirectory ) ;
FileOutputStream out = new FileOutputStream ( tempAttachmentFile ) ;
try {
message . writeTo ( out ) ;
} finally {
out . close ( ) ;
}
size = ( int ) ( tempAttachmentFile . length ( ) & 0x7FFFFFFFL ) ;
2011-02-06 17:09:48 -05:00
} else {
2010-11-13 16:40:56 -05:00
/ *
* If the attachment has a body we ' re expected to save it into the local store
* so we copy the data into a cached attachment file .
* /
InputStream in = attachment . getBody ( ) . getInputStream ( ) ;
2011-12-31 12:38:41 -05:00
try {
tempAttachmentFile = File . createTempFile ( " att " , null , attachmentDirectory ) ;
FileOutputStream out = new FileOutputStream ( tempAttachmentFile ) ;
try {
size = IOUtils . copy ( in , out ) ;
} finally {
out . close ( ) ;
}
} finally {
try { in . close ( ) ; } catch ( Throwable ignore ) { }
}
2010-11-13 16:40:56 -05:00
}
}
2010-07-11 09:44:16 -04:00
2011-02-06 17:09:48 -05:00
if ( size = = - 1 ) {
2010-11-13 16:40:56 -05:00
/ *
* If the attachment is not yet downloaded see if we can pull a size
* off the Content - Disposition .
* /
String disposition = attachment . getDisposition ( ) ;
2011-02-06 17:09:48 -05:00
if ( disposition ! = null ) {
2010-11-13 16:40:56 -05:00
String s = MimeUtility . getHeaderParameter ( disposition , " size " ) ;
2011-02-06 17:09:48 -05:00
if ( s ! = null ) {
2011-10-01 15:17:00 -04:00
try {
size = Integer . parseInt ( s ) ;
} catch ( NumberFormatException e ) { /* Ignore */ }
2010-11-13 16:40:56 -05:00
}
}
}
2011-02-06 17:09:48 -05:00
if ( size = = - 1 ) {
2010-11-13 16:40:56 -05:00
size = 0 ;
}
2008-11-01 17:32:06 -04:00
2010-11-13 16:40:56 -05:00
String storeData =
Utility . combine ( attachment . getHeader (
MimeHeader . HEADER_ANDROID_ATTACHMENT_STORE_DATA ) , ',' ) ;
2008-11-01 17:32:06 -04:00
2011-06-09 21:54:22 -04:00
String name = MimeUtility . getHeaderParameter ( attachment . getContentType ( ) , " name " ) ;
2010-11-13 16:40:56 -05:00
String contentId = MimeUtility . getHeaderParameter ( attachment . getContentId ( ) , null ) ;
2008-11-01 17:32:06 -04:00
2010-11-13 16:40:56 -05:00
String contentDisposition = MimeUtility . unfoldAndDecode ( attachment . getDisposition ( ) ) ;
2011-04-03 11:48:13 -04:00
String dispositionType = contentDisposition ;
2011-04-09 12:10:25 -04:00
if ( dispositionType ! = null ) {
int pos = dispositionType . indexOf ( ';' ) ;
if ( pos ! = - 1 ) {
// extract the disposition-type, "attachment", "inline" or extension-token (see the RFC 2183)
dispositionType = dispositionType . substring ( 0 , pos ) ;
}
2011-04-03 11:48:13 -04:00
}
2011-02-06 17:09:48 -05:00
if ( name = = null & & contentDisposition ! = null ) {
2011-06-09 21:54:22 -04:00
name = MimeUtility . getHeaderParameter ( contentDisposition , " filename " ) ;
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
if ( attachmentId = = - 1 ) {
2010-11-13 16:40:56 -05:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " message_id " , messageId ) ;
cv . put ( " content_uri " , contentUri ! = null ? contentUri . toString ( ) : null ) ;
cv . put ( " store_data " , storeData ) ;
cv . put ( " size " , size ) ;
cv . put ( " name " , name ) ;
cv . put ( " mime_type " , attachment . getMimeType ( ) ) ;
cv . put ( " content_id " , contentId ) ;
2011-04-03 11:48:13 -04:00
cv . put ( " content_disposition " , dispositionType ) ;
2010-11-13 16:40:56 -05:00
attachmentId = db . insert ( " attachments " , " message_id " , cv ) ;
2011-02-06 17:09:48 -05:00
} else {
2010-11-13 16:40:56 -05:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " content_uri " , contentUri ! = null ? contentUri . toString ( ) : null ) ;
cv . put ( " size " , size ) ;
db . update ( " attachments " , cv , " id = ? " , new String [ ]
{ Long . toString ( attachmentId ) } ) ;
}
2010-07-11 09:44:16 -04:00
2011-02-06 17:09:48 -05:00
if ( attachmentId ! = - 1 & & tempAttachmentFile ! = null ) {
2010-11-13 16:40:56 -05:00
File attachmentFile = new File ( attachmentDirectory , Long . toString ( attachmentId ) ) ;
tempAttachmentFile . renameTo ( attachmentFile ) ;
contentUri = AttachmentProvider . getAttachmentUri (
mAccount ,
attachmentId ) ;
attachment . setBody ( new LocalAttachmentBody ( contentUri , mApplication ) ) ;
ContentValues cv = new ContentValues ( ) ;
cv . put ( " content_uri " , contentUri ! = null ? contentUri . toString ( ) : null ) ;
db . update ( " attachments " , cv , " id = ? " , new String [ ]
{ Long . toString ( attachmentId ) } ) ;
}
/* The message has attachment with Content-ID */
2011-02-06 17:09:48 -05:00
if ( contentId ! = null & & contentUri ! = null ) {
2010-12-28 04:10:50 -05:00
Cursor cursor = db . query ( " messages " , new String [ ]
2011-01-06 11:55:08 -05:00
{ " html_content " } , " id = ? " , new String [ ]
{ Long . toString ( messageId ) } , null , null , null ) ;
2011-02-06 17:09:48 -05:00
try {
if ( cursor . moveToNext ( ) ) {
2011-05-06 14:02:55 -04:00
String htmlContent = cursor . getString ( 0 ) ;
2010-11-13 16:40:56 -05:00
2011-05-06 14:02:55 -04:00
if ( htmlContent ! = null ) {
String newHtmlContent = htmlContent . replaceAll (
2011-06-01 16:03:56 -04:00
Pattern . quote ( " cid: " + contentId ) ,
contentUri . toString ( ) ) ;
2010-11-13 16:40:56 -05:00
2011-05-06 14:02:55 -04:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " html_content " , newHtmlContent ) ;
db . update ( " messages " , cv , " id = ? " , new String [ ]
{ Long . toString ( messageId ) } ) ;
}
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2010-11-13 16:40:56 -05:00
}
}
2010-07-11 09:44:16 -04:00
2011-02-06 17:09:48 -05:00
if ( attachmentId ! = - 1 & & attachment instanceof LocalAttachmentBodyPart ) {
2010-11-13 16:40:56 -05:00
( ( LocalAttachmentBodyPart ) attachment ) . setAttachmentId ( attachmentId ) ;
}
return null ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
2011-02-06 17:09:48 -05:00
} catch ( IOException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
2010-07-11 09:44:16 -04:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
2010-11-13 16:40:56 -05:00
final Throwable cause = e . getCause ( ) ;
2011-02-06 17:09:48 -05:00
if ( cause instanceof IOException ) {
2012-12-07 09:50:55 -05:00
throw ( IOException ) cause ;
2010-07-11 09:44:16 -04:00
}
2012-12-07 09:50:55 -05:00
throw ( MessagingException ) cause ;
2010-07-11 09:44:16 -04:00
}
2008-11-01 17:32:06 -04:00
}
/ * *
* Changes the stored uid of the given message ( using it ' s internal id as a key ) to
* the uid in the message .
* @param message
2010-12-28 04:10:50 -05:00
* @throws com . fsck . k9 . mail . MessagingException
2008-11-01 17:32:06 -04:00
* /
2011-02-06 17:09:48 -05:00
public void changeUid ( final LocalMessage message ) throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
2010-11-13 16:40:56 -05:00
final ContentValues cv = new ContentValues ( ) ;
2008-11-01 17:32:06 -04:00
cv . put ( " uid " , message . getUid ( ) ) ;
2011-02-06 17:09:48 -05:00
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
2010-11-13 16:40:56 -05:00
db . update ( " messages " , cv , " id = ? " , new String [ ]
{ Long . toString ( message . mId ) } ) ;
return null ;
}
} ) ;
2012-10-23 18:08:44 -04:00
//TODO: remove this once the UI code exclusively uses the database id
notifyChange ( ) ;
2008-11-01 17:32:06 -04:00
}
@Override
2012-11-27 10:46:59 -05:00
public void setFlags ( final Message [ ] messages , final Flag [ ] flags , final boolean value )
2011-02-06 17:09:48 -05:00
throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
2012-11-27 10:46:59 -05:00
// Use one transaction to set all flags
try {
database . execute ( true , new DbCallback < Void > ( ) {
@Override
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException ,
UnavailableStorageException {
for ( Message message : messages ) {
try {
message . setFlags ( flags , value ) ;
} catch ( MessagingException e ) {
Log . e ( K9 . LOG_TAG , " Something went wrong while setting flag " , e ) ;
}
}
return null ;
}
} ) ;
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
}
2009-11-24 19:40:29 -05:00
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
@Override
public void setFlags ( Flag [ ] flags , boolean value )
2011-02-06 17:09:48 -05:00
throws MessagingException {
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
for ( Message message : getMessages ( null ) ) {
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
message . setFlags ( flags , value ) ;
}
}
2008-11-01 17:32:06 -04:00
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
@Override
2011-02-06 17:09:48 -05:00
public String getUidFromMessageId ( Message message ) throws MessagingException {
2009-11-24 19:40:29 -05:00
throw new MessagingException ( " Cannot call getUidFromMessageId on LocalFolder " ) ;
Complete merge of DAmail functionality into K9mail. Following
features are added to K9mail:
1) Show unread message count on each folder
2) Sum unread count of all shown folders in an account to the account display
3) Periodically check selected folders for new mail, not just Inbox
4) Don't refresh folder when opened (unless folder is empty)
5) Show date and time of last sync for each folder
6) Fix timer for automatic periodic sync (use wakelock to assure completion)
7) Optimize local folder queries (speeds up account and folder lists)
8) Show Loading... message in status bar indicating which folder is being synced
9) Eliminate redundant sync of new messages (performance enhancement)
10) Improve notification text for multiple accounts
11) Do not automatically sync folders more often than the account-specific period
12) Use user-configured date and time formats
13) Select which folders are shown, using configurable Classes
14) Select which folders are synced, using configurable Classes
15) Added context (long press) menu to folders, to provide for Refresh
and Folder Settings
16) Status light flashes purple when there are unread messages
17) Folder list more quickly eliminates display of deleted and out-of-Class folders.
18) Delete works
19) Mark all messages as read (in the folder context menu)
20) Notifications only for new unread messages
21) One minute synchronization frequency
22) Deleting an unread message decrements unread counter
23) Notifications work for POP3 accounts
24) Message deletes work for POP3 accounts
25) Explicit errors show in folder list
26) Stack traces saved to folder K9mail-errors
27) Clear pending actions (danger, for emergencies only!)
28) Delete policy in Account settings
29) DNS cache in InetAddress disabled
30) Trapped some crash-causing error conditions
31) Eliminate duplicate copies to Sent folder
32) Prevent crashes due to message listener concurrency
33) Empty Trash
34) Nuclear "Mark all messages as read" (marks all messages as read in
server-side folder, irrespective of which messages have been downloaded)
35) Forward (alternate) to allow forwarding email through other programs
36) Accept text/plain Intents to allow other programs to send email through K9mail
37) Displays Outbox sending status
38) Manual retry of outbox sending when "Refresh"ing Outbox
39) Folder error status is persisted
40) Ability to log to arbitrary file
Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104,
107, 120, 148, 154
2008-12-30 22:49:09 -05:00
}
2009-12-27 11:50:21 -05:00
2013-01-10 21:40:35 -05:00
public void clearMessagesOlderThan ( long cutoff ) throws MessagingException {
2009-11-24 19:40:29 -05:00
open ( OpenMode . READ_ONLY ) ;
2013-01-10 21:40:35 -05:00
2010-08-29 12:57:57 -04:00
Message [ ] messages = LocalStore . this . getMessages (
2010-08-30 23:58:33 -04:00
null ,
this ,
2013-01-10 21:40:35 -05:00
" SELECT " + GET_MESSAGES_COLS +
" FROM messages " +
2013-01-11 22:21:53 -05:00
" LEFT JOIN threads ON (threads.message_id = messages.id) " +
2013-01-10 21:40:35 -05:00
" WHERE (empty IS NULL OR empty != 1) AND " +
" (folder_id = ? and date < ?) " ,
new String [ ] {
Long . toString ( mFolderId ) , Long . toString ( cutoff )
} ) ;
2010-08-29 12:57:57 -04:00
2011-02-06 17:09:48 -05:00
for ( Message message : messages ) {
2013-01-10 21:40:35 -05:00
message . destroy ( ) ;
2010-08-29 12:57:57 -04:00
}
2012-10-23 18:08:44 -04:00
notifyChange ( ) ;
2009-10-21 20:41:06 -04:00
}
2009-11-24 19:40:29 -05:00
2013-01-10 21:40:35 -05:00
public void clearAllMessages ( ) throws MessagingException {
final String [ ] folderIdArg = new String [ ] { Long . toString ( mFolderId ) } ;
2010-11-13 19:49:04 -05:00
2013-01-10 21:40:35 -05:00
open ( OpenMode . READ_ONLY ) ;
2010-11-13 19:49:04 -05:00
2013-01-10 21:40:35 -05:00
try {
database . execute ( false , new DbCallback < Void > ( ) {
@Override
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException {
try {
// Get UIDs for all messages to delete
Cursor cursor = db . query ( " messages " , new String [ ] { " uid " } ,
" folder_id = ? AND (empty IS NULL OR empty != 1) " ,
folderIdArg , null , null , null ) ;
2010-11-13 19:49:04 -05:00
2013-01-10 21:40:35 -05:00
try {
// Delete attachments of these messages
while ( cursor . moveToNext ( ) ) {
deleteAttachments ( cursor . getString ( 0 ) ) ;
}
} finally {
cursor . close ( ) ;
}
2010-11-13 19:49:25 -05:00
2013-01-10 21:40:35 -05:00
// Delete entries in 'threads' and 'messages'
db . execSQL ( " DELETE FROM threads WHERE message_id IN " +
" (SELECT id FROM messages WHERE folder_id = ?) " , folderIdArg ) ;
db . execSQL ( " DELETE FROM messages WHERE folder_id = ? " , folderIdArg ) ;
2010-11-13 19:49:25 -05:00
2013-01-10 21:40:35 -05:00
return null ;
} catch ( MessagingException e ) {
throw new WrappedException ( e ) ;
}
}
} ) ;
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
}
2010-11-13 19:49:25 -05:00
2013-01-10 21:40:35 -05:00
notifyChange ( ) ;
2010-11-13 19:49:25 -05:00
setPushState ( null ) ;
setLastPush ( 0 ) ;
setLastChecked ( 0 ) ;
2010-12-29 02:42:11 -05:00
setVisibleLimit ( mAccount . getDisplayCount ( ) ) ;
2010-11-13 19:49:25 -05:00
}
2008-11-01 17:32:06 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void delete ( final boolean recurse ) throws MessagingException {
try {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
2010-11-13 16:40:56 -05:00
// We need to open the folder first to make sure we've got it's id
open ( OpenMode . READ_ONLY ) ;
Message [ ] messages = getMessages ( null ) ;
2011-02-06 17:09:48 -05:00
for ( Message message : messages ) {
2010-11-13 16:40:56 -05:00
deleteAttachments ( message . getUid ( ) ) ;
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
db . execSQL ( " DELETE FROM folders WHERE id = ? " , new Object [ ]
{ Long . toString ( mFolderId ) , } ) ;
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2010-11-13 16:40:56 -05:00
}
2008-11-01 17:32:06 -04:00
}
@Override
2011-02-06 17:09:48 -05:00
public boolean equals ( Object o ) {
if ( o instanceof LocalFolder ) {
2008-11-01 17:32:06 -04:00
return ( ( LocalFolder ) o ) . mName . equals ( mName ) ;
}
return super . equals ( o ) ;
}
2010-03-03 23:00:30 -05:00
@Override
2011-02-06 17:09:48 -05:00
public int hashCode ( ) {
2010-03-03 23:00:30 -05:00
return mName . hashCode ( ) ;
}
2011-02-06 17:09:48 -05:00
private void deleteAttachments ( final long messageId ) throws MessagingException {
2010-08-29 12:56:45 -04:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
2010-11-13 16:40:56 -05:00
Cursor attachmentsCursor = null ;
2011-02-06 17:09:48 -05:00
try {
2012-01-24 09:41:03 -05:00
String accountUuid = mAccount . getUuid ( ) ;
Context context = mApplication ;
// Get attachment IDs
String [ ] whereArgs = new String [ ] { Long . toString ( messageId ) } ;
attachmentsCursor = db . query ( " attachments " , new String [ ] { " id " } ,
" message_id = ? " , whereArgs , null , null , null ) ;
2010-11-13 16:40:56 -05:00
final File attachmentDirectory = StorageManager . getInstance ( mApplication )
2012-01-24 09:41:03 -05:00
. getAttachmentDirectory ( uUid , database . getStorageProviderId ( ) ) ;
2011-02-06 17:09:48 -05:00
while ( attachmentsCursor . moveToNext ( ) ) {
2012-01-24 09:41:03 -05:00
String attachmentId = Long . toString ( attachmentsCursor . getLong ( 0 ) ) ;
2011-02-06 17:09:48 -05:00
try {
2012-01-24 09:41:03 -05:00
// Delete stored attachment
File file = new File ( attachmentDirectory , attachmentId ) ;
2011-02-06 17:09:48 -05:00
if ( file . exists ( ) ) {
2010-11-13 16:40:56 -05:00
file . delete ( ) ;
}
2012-01-24 09:41:03 -05:00
// Delete thumbnail file
AttachmentProvider . deleteThumbnail ( context , accountUuid ,
attachmentId ) ;
} catch ( Exception e ) { /* ignore */ }
2010-08-29 12:56:45 -04:00
}
2012-01-24 09:41:03 -05:00
// Delete attachment metadata from the database
db . delete ( " attachments " , " message_id = ? " , whereArgs ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( attachmentsCursor ) ;
2010-08-29 12:56:45 -04:00
}
2010-11-13 16:40:56 -05:00
return null ;
2010-08-29 12:56:45 -04:00
}
2010-11-13 16:40:56 -05:00
} ) ;
2010-08-29 12:56:45 -04:00
}
2011-02-06 17:09:48 -05:00
private void deleteAttachments ( final String uid ) throws MessagingException {
2008-11-01 17:32:06 -04:00
open ( OpenMode . READ_WRITE ) ;
2011-02-06 17:09:48 -05:00
try {
database . execute ( false , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
2010-11-13 16:40:56 -05:00
Cursor messagesCursor = null ;
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
messagesCursor = db . query ( " messages " , new String [ ]
{ " id " } , " folder_id = ? AND uid = ? " , new String [ ]
{ Long . toString ( mFolderId ) , uid } , null , null , null ) ;
2011-02-06 17:09:48 -05:00
while ( messagesCursor . moveToNext ( ) ) {
2010-11-13 16:40:56 -05:00
long messageId = messagesCursor . getLong ( 0 ) ;
deleteAttachments ( messageId ) ;
2008-11-01 17:32:06 -04:00
2010-11-13 16:40:56 -05:00
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( messagesCursor ) ;
2010-11-13 16:40:56 -05:00
}
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2008-11-01 17:32:06 -04:00
}
}
2009-05-03 16:52:32 -04:00
2010-03-07 12:02:21 -05:00
@Override
2011-02-06 17:09:48 -05:00
public boolean isInTopGroup ( ) {
2011-01-15 23:23:03 -05:00
return mInTopGroup ;
2010-03-07 12:02:21 -05:00
}
2010-04-29 00:59:14 -04:00
2011-02-06 17:09:48 -05:00
public void setInTopGroup ( boolean inTopGroup ) throws MessagingException {
2011-01-15 23:23:03 -05:00
mInTopGroup = inTopGroup ;
2011-02-06 17:09:48 -05:00
updateFolderColumn ( " top_group " , mInTopGroup ? 1 : 0 ) ;
2010-03-07 12:02:21 -05:00
}
2011-01-02 04:01:23 -05:00
2011-02-06 17:09:48 -05:00
public Integer getLastUid ( ) {
2011-01-02 04:01:23 -05:00
return mLastUid ;
}
2011-01-02 22:26:31 -05:00
/ * *
* < p > Fetches the most recent < b > numeric < / b > UID value in this folder . This is used by
* { @link com . fsck . k9 . controller . MessagingController # shouldNotifyForMessage } to see if messages being
* fetched are new and unread . Messages are " new " if they have a UID higher than the most recent UID prior
* to synchronization . < / p >
*
* < p > This only works for protocols with numeric UIDs ( like IMAP ) . For protocols with
* alphanumeric UIDs ( like POP ) , this method quietly fails and shouldNotifyForMessage ( ) will
* always notify for unread messages . < / p >
*
* < p > Once Issue 1072 has been fixed , this method and shouldNotifyForMessage ( ) should be
* updated to use internal dates rather than UIDs to determine new - ness . While this doesn ' t
* solve things for POP ( which doesn ' t have internal dates ) , we can likely use this as a
* framework to examine send date in lieu of internal date . < / p >
* @throws MessagingException
* /
2011-02-06 17:09:48 -05:00
public void updateLastUid ( ) throws MessagingException {
Integer lastUid = database . execute ( false , new DbCallback < Integer > ( ) {
2011-01-02 04:01:23 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Integer doDbWork ( final SQLiteDatabase db ) {
2011-01-02 04:01:23 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2011-01-02 04:01:23 -05:00
open ( OpenMode . READ_ONLY ) ;
cursor = db . rawQuery ( " SELECT MAX(uid) FROM messages WHERE folder_id=? " , new String [ ] { Long . toString ( mFolderId ) } ) ;
2011-02-06 17:09:48 -05:00
if ( cursor . getCount ( ) > 0 ) {
2011-01-02 04:01:23 -05:00
cursor . moveToFirst ( ) ;
return cursor . getInt ( 0 ) ;
}
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2011-01-02 04:01:23 -05:00
Log . e ( K9 . LOG_TAG , " Unable to updateLastUid: " , e ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2011-01-02 04:01:23 -05:00
}
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
if ( K9 . DEBUG )
2011-01-02 04:01:23 -05:00
Log . d ( K9 . LOG_TAG , " Updated last UID for folder " + mName + " to " + lastUid ) ;
mLastUid = lastUid ;
}
2011-01-23 22:27:14 -05:00
2011-11-27 13:29:45 -05:00
public Long getOldestMessageDate ( ) throws MessagingException {
2011-02-06 17:09:48 -05:00
return database . execute ( false , new DbCallback < Long > ( ) {
2011-01-23 22:27:14 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Long doDbWork ( final SQLiteDatabase db ) {
2011-01-23 22:27:14 -05:00
Cursor cursor = null ;
2011-02-06 17:09:48 -05:00
try {
2011-01-23 22:27:14 -05:00
open ( OpenMode . READ_ONLY ) ;
cursor = db . rawQuery ( " SELECT MIN(date) FROM messages WHERE folder_id=? " , new String [ ] { Long . toString ( mFolderId ) } ) ;
2011-02-06 17:09:48 -05:00
if ( cursor . getCount ( ) > 0 ) {
2011-01-23 22:27:14 -05:00
cursor . moveToFirst ( ) ;
return cursor . getLong ( 0 ) ;
}
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2011-01-23 22:27:14 -05:00
Log . e ( K9 . LOG_TAG , " Unable to fetch oldest message date: " , e ) ;
2011-02-06 17:09:48 -05:00
} finally {
2011-10-20 02:25:27 -04:00
Utility . closeQuietly ( cursor ) ;
2011-01-23 22:27:14 -05:00
}
return null ;
}
} ) ;
}
2012-10-08 16:51:29 -04:00
private ThreadInfo doMessageThreading ( SQLiteDatabase db , Message message )
throws MessagingException {
long rootId = - 1 ;
long parentId = - 1 ;
String messageId = message . getMessageId ( ) ;
// If there's already an empty message in the database, update that
2013-01-10 21:40:35 -05:00
ThreadInfo msgThreadInfo = getThreadInfo ( db , messageId ) ;
2012-10-08 16:51:29 -04:00
// Get the message IDs from the "References" header line
String [ ] referencesArray = message . getHeader ( " References " ) ;
List < String > messageIds = null ;
if ( referencesArray ! = null & & referencesArray . length > 0 ) {
messageIds = Utility . extractMessageIds ( referencesArray [ 0 ] ) ;
}
// Append the first message ID from the "In-Reply-To" header line
String [ ] inReplyToArray = message . getHeader ( " In-Reply-To " ) ;
String inReplyTo = null ;
if ( inReplyToArray ! = null & & inReplyToArray . length > 0 ) {
inReplyTo = Utility . extractMessageId ( inReplyToArray [ 0 ] ) ;
if ( inReplyTo ! = null ) {
if ( messageIds = = null ) {
messageIds = new ArrayList < String > ( 1 ) ;
messageIds . add ( inReplyTo ) ;
} else if ( ! messageIds . contains ( inReplyTo ) ) {
messageIds . add ( inReplyTo ) ;
}
}
}
if ( messageIds = = null ) {
// This is not a reply, nothing to do for us.
2013-01-10 21:40:35 -05:00
return ( msgThreadInfo ! = null ) ?
msgThreadInfo : new ThreadInfo ( - 1 , - 1 , messageId , - 1 , - 1 ) ;
2012-10-08 16:51:29 -04:00
}
for ( String reference : messageIds ) {
ThreadInfo threadInfo = getThreadInfo ( db , reference ) ;
if ( threadInfo = = null ) {
2013-01-10 21:40:35 -05:00
// Create placeholder message in 'messages' table
2012-10-08 16:51:29 -04:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " message_id " , reference ) ;
cv . put ( " folder_id " , mFolderId ) ;
cv . put ( " empty " , 1 ) ;
2013-01-10 21:40:35 -05:00
long newMsgId = db . insert ( " messages " , null , cv ) ;
// Create entry in 'threads' table
cv . clear ( ) ;
cv . put ( " message_id " , newMsgId ) ;
2012-10-08 16:51:29 -04:00
if ( rootId ! = - 1 ) {
2013-01-10 21:40:35 -05:00
cv . put ( " root " , rootId ) ;
2012-10-08 16:51:29 -04:00
}
if ( parentId ! = - 1 ) {
2013-01-10 21:40:35 -05:00
cv . put ( " parent " , parentId ) ;
2012-10-08 16:51:29 -04:00
}
2013-01-10 21:40:35 -05:00
parentId = db . insert ( " threads " , null , cv ) ;
2012-10-08 16:51:29 -04:00
if ( rootId = = - 1 ) {
rootId = parentId ;
}
} else {
2013-01-10 21:40:35 -05:00
if ( rootId ! = - 1 & & threadInfo . rootId = = - 1 & & rootId ! = threadInfo . threadId ) {
2012-10-08 16:51:29 -04:00
// We found an existing root container that is not
// the root of our current path (References).
// Connect it to the current parent.
// Let all children know who's the new root
ContentValues cv = new ContentValues ( ) ;
2013-01-10 21:40:35 -05:00
cv . put ( " root " , rootId ) ;
db . update ( " threads " , cv , " root = ? " ,
new String [ ] { Long . toString ( threadInfo . threadId ) } ) ;
2012-10-08 16:51:29 -04:00
// Connect the message to the current parent
2013-01-10 21:40:35 -05:00
cv . put ( " parent " , parentId ) ;
db . update ( " threads " , cv , " id = ? " ,
new String [ ] { Long . toString ( threadInfo . threadId ) } ) ;
2012-10-08 16:51:29 -04:00
} else {
2013-01-10 21:40:35 -05:00
rootId = ( threadInfo . rootId = = - 1 ) ?
threadInfo . threadId : threadInfo . rootId ;
2012-10-08 16:51:29 -04:00
}
2013-01-10 21:40:35 -05:00
parentId = threadInfo . threadId ;
2012-10-08 16:51:29 -04:00
}
}
//TODO: set in-reply-to "link" even if one already exists
2013-01-10 21:40:35 -05:00
long threadId ;
long msgId ;
if ( msgThreadInfo ! = null ) {
threadId = msgThreadInfo . threadId ;
msgId = msgThreadInfo . msgId ;
} else {
threadId = - 1 ;
msgId = - 1 ;
}
return new ThreadInfo ( threadId , msgId , messageId , rootId , parentId ) ;
2012-10-08 16:51:29 -04:00
}
2012-11-15 15:05:45 -05:00
public List < Message > extractNewMessages ( final List < Message > messages )
throws MessagingException {
try {
return database . execute ( false , new DbCallback < List < Message > > ( ) {
@Override
public List < Message > doDbWork ( final SQLiteDatabase db ) throws WrappedException {
try {
open ( OpenMode . READ_WRITE ) ;
} catch ( MessagingException e ) {
throw new WrappedException ( e ) ;
}
List < Message > result = new ArrayList < Message > ( ) ;
List < String > selectionArgs = new ArrayList < String > ( ) ;
Set < String > existingMessages = new HashSet < String > ( ) ;
int start = 0 ;
while ( start < messages . size ( ) ) {
StringBuilder selection = new StringBuilder ( ) ;
selection . append ( " folder_id = ? AND UID IN ( " ) ;
selectionArgs . add ( Long . toString ( mFolderId ) ) ;
int count = Math . min ( messages . size ( ) - start , UID_CHECK_BATCH_SIZE ) ;
for ( int i = start , end = start + count ; i < end ; i + + ) {
if ( i > start ) {
selection . append ( " ,? " ) ;
} else {
selection . append ( " ? " ) ;
}
selectionArgs . add ( messages . get ( i ) . getUid ( ) ) ;
}
selection . append ( " ) " ) ;
Cursor cursor = db . query ( " messages " , UID_CHECK_PROJECTION ,
selection . toString ( ) , selectionArgs . toArray ( EMPTY_STRING_ARRAY ) ,
null , null , null ) ;
try {
while ( cursor . moveToNext ( ) ) {
String uid = cursor . getString ( 0 ) ;
existingMessages . add ( uid ) ;
}
} finally {
Utility . closeQuietly ( cursor ) ;
}
for ( int i = start , end = start + count ; i < end ; i + + ) {
Message message = messages . get ( i ) ;
if ( ! existingMessages . contains ( message . getUid ( ) ) ) {
result . add ( message ) ;
}
}
existingMessages . clear ( ) ;
selectionArgs . clear ( ) ;
start + = count ;
}
return result ;
}
} ) ;
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
}
}
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public static class LocalTextBody extends TextBody {
2010-12-29 02:34:57 -05:00
/ * *
* This is an HTML - ified version of the message for display purposes .
* /
2009-05-20 00:36:20 -04:00
private String mBodyForDisplay ;
2011-02-06 17:09:48 -05:00
public LocalTextBody ( String body ) {
2009-05-20 00:36:20 -04:00
super ( body ) ;
}
2011-02-06 17:09:48 -05:00
public LocalTextBody ( String body , String bodyForDisplay ) {
2009-05-20 00:36:20 -04:00
super ( body ) ;
this . mBodyForDisplay = bodyForDisplay ;
}
2011-02-06 17:09:48 -05:00
public String getBodyForDisplay ( ) {
2009-05-20 00:36:20 -04:00
return mBodyForDisplay ;
}
2011-02-06 17:09:48 -05:00
public void setBodyForDisplay ( String mBodyForDisplay ) {
2009-05-20 00:36:20 -04:00
this . mBodyForDisplay = mBodyForDisplay ;
}
} //LocalTextBody
2011-02-06 17:09:48 -05:00
public class LocalMessage extends MimeMessage {
2008-11-01 17:32:06 -04:00
private long mId ;
private int mAttachmentCount ;
2009-12-01 08:45:28 -05:00
private String mSubject ;
2010-01-12 22:36:36 -05:00
private String mPreview = " " ;
2009-12-14 21:51:18 -05:00
private boolean mHeadersLoaded = false ;
2009-12-06 23:46:42 -05:00
private boolean mMessageDirty = false ;
2013-01-10 21:40:35 -05:00
private long mThreadId ;
2012-10-08 16:51:29 -04:00
private long mRootId ;
2011-02-06 17:09:48 -05:00
public LocalMessage ( ) {
2009-12-06 23:46:42 -05:00
}
2011-02-06 17:09:48 -05:00
LocalMessage ( String uid , Folder folder ) {
2009-12-06 23:46:42 -05:00
this . mUid = uid ;
this . mFolder = folder ;
}
2009-12-27 11:52:57 -05:00
private void populateFromGetMessageCursor ( Cursor cursor )
2011-02-06 17:09:48 -05:00
throws MessagingException {
2010-10-02 14:45:51 -04:00
final String subject = cursor . getString ( 0 ) ;
this . setSubject ( subject = = null ? " " : subject ) ;
2009-12-27 11:52:57 -05:00
Address [ ] from = Address . unpack ( cursor . getString ( 1 ) ) ;
2011-02-06 17:09:48 -05:00
if ( from . length > 0 ) {
2009-12-27 11:52:57 -05:00
this . setFrom ( from [ 0 ] ) ;
}
this . setInternalSentDate ( new Date ( cursor . getLong ( 2 ) ) ) ;
this . setUid ( cursor . getString ( 3 ) ) ;
String flagList = cursor . getString ( 4 ) ;
2011-02-06 17:09:48 -05:00
if ( flagList ! = null & & flagList . length ( ) > 0 ) {
2009-12-27 11:52:57 -05:00
String [ ] flags = flagList . split ( " , " ) ;
2010-04-29 00:59:14 -04:00
2011-02-06 17:09:48 -05:00
for ( String flag : flags ) {
try {
2009-12-27 11:52:57 -05:00
this . setFlagInternal ( Flag . valueOf ( flag ) , true ) ;
}
2010-04-29 00:59:14 -04:00
2011-02-06 17:09:48 -05:00
catch ( Exception e ) {
if ( ! " X_BAD_FLAG " . equals ( flag ) ) {
2010-04-25 02:17:15 -04:00
Log . w ( K9 . LOG_TAG , " Unable to parse flag " + flag ) ;
}
}
2009-12-27 11:52:57 -05:00
}
}
this . mId = cursor . getLong ( 5 ) ;
this . setRecipients ( RecipientType . TO , Address . unpack ( cursor . getString ( 6 ) ) ) ;
this . setRecipients ( RecipientType . CC , Address . unpack ( cursor . getString ( 7 ) ) ) ;
this . setRecipients ( RecipientType . BCC , Address . unpack ( cursor . getString ( 8 ) ) ) ;
this . setReplyTo ( Address . unpack ( cursor . getString ( 9 ) ) ) ;
2010-04-05 22:54:48 -04:00
2009-12-27 11:52:57 -05:00
this . mAttachmentCount = cursor . getInt ( 10 ) ;
this . setInternalDate ( new Date ( cursor . getLong ( 11 ) ) ) ;
this . setMessageId ( cursor . getString ( 12 ) ) ;
2010-10-02 14:45:51 -04:00
final String preview = cursor . getString ( 14 ) ;
mPreview = ( preview = = null ? " " : preview ) ;
2011-02-06 17:09:48 -05:00
if ( this . mFolder = = null ) {
2009-12-27 11:53:51 -05:00
LocalFolder f = new LocalFolder ( cursor . getInt ( 13 ) ) ;
f . open ( LocalFolder . OpenMode . READ_WRITE ) ;
this . mFolder = f ;
}
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
mThreadId = ( cursor . isNull ( 15 ) ) ? - 1 : cursor . getLong ( 15 ) ;
mRootId = ( cursor . isNull ( 16 ) ) ? - 1 : cursor . getLong ( 16 ) ;
2012-12-03 23:13:58 -05:00
boolean deleted = ( cursor . getInt ( 17 ) = = 1 ) ;
boolean read = ( cursor . getInt ( 18 ) = = 1 ) ;
boolean flagged = ( cursor . getInt ( 19 ) = = 1 ) ;
boolean answered = ( cursor . getInt ( 20 ) = = 1 ) ;
boolean forwarded = ( cursor . getInt ( 21 ) = = 1 ) ;
setFlagInternal ( Flag . DELETED , deleted ) ;
setFlagInternal ( Flag . SEEN , read ) ;
setFlagInternal ( Flag . FLAGGED , flagged ) ;
setFlagInternal ( Flag . ANSWERED , answered ) ;
setFlagInternal ( Flag . FORWARDED , forwarded ) ;
2009-12-27 11:52:57 -05:00
}
2010-12-29 02:34:57 -05:00
/ * *
* Fetch the message text for display . This always returns an HTML - ified version of the
* message , even if it was originally a text - only message .
2011-02-19 10:35:11 -05:00
* @return HTML version of message for display purposes or null .
2010-12-29 02:34:57 -05:00
* @throws MessagingException
* /
2011-02-06 17:09:48 -05:00
public String getTextForDisplay ( ) throws MessagingException {
2011-02-19 10:35:11 -05:00
String text = null ; // First try and fetch an HTML part.
2010-12-28 04:07:39 -05:00
Part part = MimeUtility . findFirstPartByMimeType ( this , " text/html " ) ;
2011-02-06 17:09:48 -05:00
if ( part = = null ) {
2010-12-28 04:07:39 -05:00
// If that fails, try and get a text part.
part = MimeUtility . findFirstPartByMimeType ( this , " text/plain " ) ;
2011-02-19 10:35:11 -05:00
if ( part ! = null & & part . getBody ( ) instanceof LocalStore . LocalTextBody ) {
text = ( ( LocalStore . LocalTextBody ) part . getBody ( ) ) . getBodyForDisplay ( ) ;
2010-12-28 04:07:39 -05:00
}
2011-02-06 17:09:48 -05:00
} else {
2010-12-28 04:07:39 -05:00
// We successfully found an HTML part; do the necessary character set decoding.
text = MimeUtility . getTextFromPart ( part ) ;
}
return text ;
}
2009-12-06 23:46:42 -05:00
/ * Custom version of writeTo that updates the MIME message based on localMessage
* changes .
* /
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void writeTo ( OutputStream out ) throws IOException , MessagingException {
2009-12-06 23:46:42 -05:00
if ( mMessageDirty ) buildMimeRepresentation ( ) ;
super . writeTo ( out ) ;
}
2011-02-06 17:09:48 -05:00
private void buildMimeRepresentation ( ) throws MessagingException {
if ( ! mMessageDirty ) {
2009-12-06 23:46:42 -05:00
return ;
2009-12-01 08:45:28 -05:00
}
2009-12-06 23:46:42 -05:00
super . setSubject ( mSubject ) ;
2011-02-06 17:09:48 -05:00
if ( this . mFrom ! = null & & this . mFrom . length > 0 ) {
2009-12-06 23:46:42 -05:00
super . setFrom ( this . mFrom [ 0 ] ) ;
2009-12-01 08:45:28 -05:00
}
2009-12-06 23:46:42 -05:00
super . setReplyTo ( mReplyTo ) ;
super . setSentDate ( this . getSentDate ( ) ) ;
super . setRecipients ( RecipientType . TO , mTo ) ;
super . setRecipients ( RecipientType . CC , mCc ) ;
super . setRecipients ( RecipientType . BCC , mBcc ) ;
2009-12-07 23:58:10 -05:00
if ( mMessageId ! = null ) super . setMessageId ( mMessageId ) ;
2009-12-06 23:46:42 -05:00
mMessageDirty = false ;
2009-12-01 08:45:28 -05:00
}
2012-12-07 09:50:55 -05:00
@Override
2011-02-06 17:09:48 -05:00
public String getPreview ( ) {
2010-01-13 20:07:28 -05:00
return mPreview ;
2010-01-12 22:36:36 -05:00
}
2009-12-06 23:46:42 -05:00
2009-12-01 08:45:28 -05:00
@Override
2011-02-06 17:09:48 -05:00
public String getSubject ( ) {
2009-12-06 23:46:42 -05:00
return mSubject ;
2009-12-01 08:45:28 -05:00
}
2009-12-06 23:46:42 -05:00
@Override
2011-02-06 17:09:48 -05:00
public void setSubject ( String subject ) throws MessagingException {
2009-12-06 23:46:42 -05:00
mSubject = subject ;
mMessageDirty = true ;
r62972@17h: jesse | 2009-05-07 10:49:32 -0400
First stab at a folderlist that doesn't know or care about messages
r62973@17h: jesse | 2009-05-07 10:50:11 -0400
A very broken first stab at a message list that only knows about one folder.
r62974@17h: jesse | 2009-05-07 10:50:44 -0400
When you go from an account list to an individual account, open a folderlist, not an fml
r62975@17h: jesse | 2009-05-07 10:51:24 -0400
Update Welcome activity to open an ml instead of an fml
r62976@17h: jesse | 2009-05-07 10:51:59 -0400
When setting up accounts is over, open an fl instead of an fml
r62977@17h: jesse | 2009-05-07 10:52:51 -0400
Update MessageView to use folderinfoholders and messageinfoholders from the 'correct' classes.
r62978@17h: jesse | 2009-05-07 10:59:07 -0400
MailService now notifies the fl instead of the fml. Not sure if it should also notify the ml. - will require testing
r62979@17h: jesse | 2009-05-07 11:01:09 -0400
Switch MessagingController's notifications from notifying the FML to notifying an ML
r62980@17h: jesse | 2009-05-07 11:25:22 -0400
Update AndroidManifest to know about the new world order
r62981@17h: jesse | 2009-05-07 11:26:11 -0400
Try to follow the android sdk docs for intent creation
r62982@17h: jesse | 2009-05-07 11:28:30 -0400
reset MessageList for another try at the conversion
r62983@17h: jesse | 2009-05-07 11:47:33 -0400
This version doesn't crash and has a working 'folder' layer. now to clean up the message list layer
r62984@17h: jesse | 2009-05-07 15:18:04 -0400
move step 1
r62985@17h: jesse | 2009-05-07 15:18:37 -0400
move step 1
r62986@17h: jesse | 2009-05-07 15:22:47 -0400
rename step 1
r62987@17h: jesse | 2009-05-07 17:38:02 -0400
checkpoint to move
r62988@17h: jesse | 2009-05-07 17:40:01 -0400
checkpointing a state with a working folder list and a message list that doesn't explode
r62989@17h: jesse | 2009-05-07 17:40:26 -0400
Remove debugging cruft from Welcome
r62990@17h: jesse | 2009-05-07 22:00:12 -0400
Basic functionality works.
r62991@17h: jesse | 2009-05-08 04:19:52 -0400
added a tool to build a K-9 "Beta"
r62992@17h: jesse | 2009-05-08 04:20:03 -0400
remove a disused file
r62993@17h: jesse | 2009-05-09 06:07:02 -0400
upgrading build infrastructure for the 1.5 sdk
r62994@17h: jesse | 2009-05-09 06:22:02 -0400
further refine onOpenMessage, removing more folder assumptions
r62995@17h: jesse | 2009-05-09 20:07:20 -0400
Make the Welcome activity open the autoexpandfolder rather than INBOX
r62996@17h: jesse | 2009-05-09 20:14:10 -0400
MessageList now stores the Folder name it was working with across pause-reload
r62997@17h: jesse | 2009-05-09 20:14:26 -0400
Removing dead code from FolderList
r63060@17h: jesse | 2009-05-10 00:07:33 -0400
Replace the old message list refreshing code which cleared and rebuilt the list from scratch with code which updates or deletes existing messages.
Add "go back to folder list" code
r63061@17h: jesse | 2009-05-10 00:07:50 -0400
fix message list menus for new world order
r63062@17h: jesse | 2009-05-10 00:08:11 -0400
Remove message list options from folder list menus
r63063@17h: jesse | 2009-05-10 00:10:02 -0400
remove more message list options from the folder list
r63064@17h: jesse | 2009-05-10 00:10:19 -0400
fix build.xml for the new android world order
r63065@17h: jesse | 2009-05-10 00:39:23 -0400
reformatted in advance of bug tracing
r63066@17h: jesse | 2009-05-10 05:53:28 -0400
fix our 'close' behavior to not leave extra activities around
clean up more vestigal code
r63067@17h: jesse | 2009-05-10 18:44:25 -0400
Improve "back button / accounts" workflow from FolderList -> AccountList
r63068@17h: jesse | 2009-05-10 19:11:47 -0400
* Add required code for the 'k9beta' build
r63069@17h: jesse | 2009-05-10 19:12:05 -0400
Make the folder list white backgrounded.
r63070@17h: jesse | 2009-05-10 19:12:26 -0400
* Include our required libraries in build.xml
r63071@17h: jesse | 2009-05-10 19:13:07 -0400
Added directories for our built code and our generated code
r63072@17h: jesse | 2009-05-10 19:13:36 -0400
Added a "back" button image
r63073@17h: jesse | 2009-05-10 20:13:50 -0400
Switch next/prev buttons to triangles for I18N and eventual "more easy-to-hit buttons" win
r63074@17h: jesse | 2009-05-10 20:17:18 -0400
Tidy Accounts.java for some perf hacking.
r63081@17h: jesse | 2009-05-10 22:13:33 -0400
First pass reformatting of the MessagingController
r63082@17h: jesse | 2009-05-10 23:50:28 -0400
MessageList now correctly updates when a background sync happens
r63083@17h: jesse | 2009-05-10 23:50:53 -0400
Tidying FolderList
r63084@17h: jesse | 2009-05-10 23:51:09 -0400
tidy
r63085@17h: jesse | 2009-05-10 23:51:27 -0400
tidy
r63086@17h: jesse | 2009-05-11 00:17:06 -0400
Properly update unread counts in the FolderList after sync
r63087@17h: jesse | 2009-05-11 01:38:14 -0400
Minor refactoring for readability. replace a boolean with a constant.
r63090@17h: jesse | 2009-05-11 02:58:31 -0400
now that the foreground of message lists is light, we don't need the light messagebox
r63091@17h: jesse | 2009-05-11 17:15:02 -0400
Added a string for "back to folder list"
r63092@17h: jesse | 2009-05-11 17:15:24 -0400
Added a message list header with a back button
r63093@17h: jesse | 2009-05-11 17:15:54 -0400
Remove the "folder list" button from the options menu. no sense duplicating it
r63094@17h: jesse | 2009-05-11 17:17:06 -0400
Refactored views, adding our replacement scrollable header
r63184@17h: jesse | 2009-05-12 07:07:15 -0400
fix weird bug where message lists could show a header element for a child
r63185@17h: jesse | 2009-05-12 07:08:12 -0400
Add new-style headers to folder lists. reimplement "get folder by name" to not use a bloody for loop
r63211@17h: jesse | 2009-05-12 18:37:48 -0400
Restore the former glory of the "load more messages" widget. it still needs an overhaul
r63296@17h: jesse | 2009-05-12 23:23:21 -0400
Get the indeterminate progress bar to show up again when you click "get more messages"
r63297@17h: jesse | 2009-05-13 02:40:39 -0400
Fixed off-by-one errors in click and keybindings for messagelist
r63298@17h: jesse | 2009-05-13 06:04:01 -0400
Put the folder title in the name of the folderSettings popup
r63299@17h: jesse | 2009-05-13 06:04:49 -0400
Reformatting. Removing debug logging
r63300@17h: jesse | 2009-05-13 06:05:32 -0400
Fixing "wrong item selected" bugs in the FolderList
r63328@17h: jesse | 2009-05-13 13:20:00 -0400
Update MessageView for 1.5
r63329@17h: jesse | 2009-05-13 13:50:29 -0400
A couple fixes to "picking the right item"
Titles on the message context menu
r63330@17h: jesse | 2009-05-13 13:58:37 -0400
Added an "open" context menu item to the folder list
r63347@17h: jesse | 2009-05-13 18:00:02 -0400
Try to get folderlists to sort in a stable way, so they jump around less in the ui
r63349@17h: jesse | 2009-05-13 20:37:19 -0400
Switch to using non-message-passing based notifications for redisplay of message lists, cut down redisplay frequency to not overload the display
r63432@17h: jesse | 2009-05-16 13:38:49 -0400
Android 1.5 no longer gives us apache.commons.codec by default and apache.commons.logging by default. Import them so we have em.
There's probably something smarter to do here.
r63438@17h: jesse | 2009-05-16 14:12:06 -0400
removed dead code
r63439@17h: jesse | 2009-05-16 14:30:57 -0400
Minor tidy
r63440@17h: jesse | 2009-05-16 14:39:34 -0400
First pass implementation making MessageList streamy for faster startup
r63441@17h: jesse | 2009-05-16 21:57:41 -0400
There's no reason for the FolderList to list local messages
r63442@17h: jesse | 2009-05-16 21:58:57 -0400
Switch to actually refreshing the message list after each item is loaded
r63450@17h: jesse | 2009-05-16 22:34:18 -0400
Default to pulling items out of the LocalStore by date, descending. (since that's the uneditable default ordering)
This makes our messages come out of the store in the order the user should see them
r63451@17h: jesse | 2009-05-16 22:34:44 -0400
Set some new defaults for the FolderList
r63452@17h: jesse | 2009-05-16 22:35:43 -0400
set some new message list item defaults
r63456@17h: jesse | 2009-05-17 12:56:10 -0400
It's not clear that Pop and WebDav actually set us an InternalDate. I'd rather use that so that spam doesn't topsort. But I also want this to _work_
r63457@17h: jesse | 2009-05-17 12:56:47 -0400
actually check to make sure we have a message to remove before removing it.
r63458@17h: jesse | 2009-05-17 13:10:07 -0400
Flip "security type" to before the port number, since changing security type is the thing more users are likely to know/care about and resets port number
r63469@17h: jesse | 2009-05-17 18:42:39 -0400
Provisional fix for "see the FoldeRList twice" bug
r63471@17h: jesse | 2009-05-17 20:47:41 -0400
Remove title bar from the message view
r63544@17h: jesse | 2009-05-20 23:53:38 -0400
folderlist tidying before i dig into the jumpy ordering bug
r63545@17h: jesse | 2009-05-20 23:56:00 -0400
Killing dead variables
r63546@17h: jesse | 2009-05-21 00:58:36 -0400
make the whole title section clicky
r63556@17h: jesse | 2009-05-21 01:48:13 -0400
Fix where we go when someone deletes a message
r63558@17h: jesse | 2009-05-21 22:44:46 -0400
Working toward switchable themes
r63563@17h: jesse | 2009-05-21 23:53:09 -0400
Make the MessageList's colors actually just inherit from the theme, rather than hardcoding black
r63567@17h: jesse | 2009-05-22 10:14:13 -0400
Kill a now-redundant comment
r63571@17h: jesse | 2009-05-22 19:43:30 -0400
further theme-independence work
r63572@17h: jesse | 2009-05-22 19:55:23 -0400
gete -> get (typo fix)
r63573@17h: jesse | 2009-05-22 22:48:49 -0400
First cut of a global prefs system as well as a theme preference. not that it works yet
r63577@17h: jesse | 2009-05-24 14:49:52 -0400
Once a user has actually put in valid user credentials, start syncing mail and folders in the background instantly.
This gives us a much better "new startup" experience
r63578@17h: jesse | 2009-05-24 14:55:00 -0400
MessageList doesn't need FolderUpdateWorker
r63579@17h: jesse | 2009-05-24 17:57:15 -0400
Fix "get message by uid"
Switch to showing messages 10 by 10, rather than 1 by 1 for huge loadtime performance improvements
r63587@17h: jesse | 2009-05-24 19:19:56 -0400
Cut down LocalMessage creation to not generate a MessageId or date formatter.
r63589@17h: jesse | 2009-05-24 22:22:32 -0400
Switch to null-escaping email address boundaries, rather than a VERY expensive URL-encoding
r63590@17h: jesse | 2009-05-24 22:23:21 -0400
Clean up our "auto-refresh the list when adding messages after a sync"
r63593@17h: jesse | 2009-05-24 22:53:45 -0400
replace isDateToday with a "rolling 18 hour window" variant that's more likely to give the user a useful answer and is 30x faster.
r63595@17h: jesse | 2009-05-24 23:54:14 -0400
When instantiating messges from the LocalStore, there's no need to clear headers before setting them, nor is there a need to set a generated message id
r63596@17h: jesse | 2009-05-24 23:54:39 -0400
make an overridable setGeneratedMessageId
r63597@17h: jesse | 2009-05-24 23:54:55 -0400
Remove new lies from comments
r63598@17h: jesse | 2009-05-24 23:55:35 -0400
Replace insanely expensive message header "name" part quoting with something consistent and cheap that does its work on the way INTO the database
r63605@17h: jesse | 2009-05-25 17:28:24 -0400
bring back the 1.1 sdk build.xml
r63606@17h: jesse | 2009-05-25 22:32:11 -0400
Actually enable switchable themese and compilation on 1.1
r63692@17h: jesse | 2009-05-29 23:55:17 -0400
Switch back to having titles for folder and message lists.
Restore auto-open-folder functionality
r63694@17h: jesse | 2009-05-30 18:50:39 -0400
Remove several off-by-one errors introduced by yesterday's return to android titlebars
r63696@17h: jesse | 2009-05-30 23:45:03 -0400
use convertView properly for performance and memory imrpovement in FolderList and MessageList
r63698@17h: jesse | 2009-05-31 19:42:59 -0400
Switch to using background shading to indicate "not yet fetched"
r63701@17h: jesse | 2009-05-31 21:28:47 -0400
Remving code we don't actually need these bits of apache commons on 1.1
2009-05-31 21:35:05 -04:00
}
2008-11-01 17:32:06 -04:00
2009-12-06 23:46:42 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setMessageId ( String messageId ) {
2009-12-06 23:46:42 -05:00
mMessageId = messageId ;
mMessageDirty = true ;
2008-11-01 17:32:06 -04:00
}
2009-11-24 19:40:29 -05:00
2012-12-07 09:50:55 -05:00
@Override
2011-02-06 17:09:48 -05:00
public boolean hasAttachments ( ) {
2012-12-07 09:50:55 -05:00
return ( mAttachmentCount > 0 ) ;
2010-11-26 23:02:56 -05:00
}
2009-12-06 23:46:42 -05:00
2011-02-06 17:09:48 -05:00
public int getAttachmentCount ( ) {
2008-11-01 17:32:06 -04:00
return mAttachmentCount ;
}
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setFrom ( Address from ) throws MessagingException {
2009-12-06 23:46:42 -05:00
this . mFrom = new Address [ ] { from } ;
mMessageDirty = true ;
2008-11-01 17:32:06 -04:00
}
2009-12-06 23:46:42 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setReplyTo ( Address [ ] replyTo ) throws MessagingException {
if ( replyTo = = null | | replyTo . length = = 0 ) {
2009-12-06 23:46:42 -05:00
mReplyTo = null ;
2011-02-06 17:09:48 -05:00
} else {
2009-12-06 23:46:42 -05:00
mReplyTo = replyTo ;
2009-11-24 19:40:29 -05:00
}
2009-12-06 23:46:42 -05:00
mMessageDirty = true ;
r62972@17h: jesse | 2009-05-07 10:49:32 -0400
First stab at a folderlist that doesn't know or care about messages
r62973@17h: jesse | 2009-05-07 10:50:11 -0400
A very broken first stab at a message list that only knows about one folder.
r62974@17h: jesse | 2009-05-07 10:50:44 -0400
When you go from an account list to an individual account, open a folderlist, not an fml
r62975@17h: jesse | 2009-05-07 10:51:24 -0400
Update Welcome activity to open an ml instead of an fml
r62976@17h: jesse | 2009-05-07 10:51:59 -0400
When setting up accounts is over, open an fl instead of an fml
r62977@17h: jesse | 2009-05-07 10:52:51 -0400
Update MessageView to use folderinfoholders and messageinfoholders from the 'correct' classes.
r62978@17h: jesse | 2009-05-07 10:59:07 -0400
MailService now notifies the fl instead of the fml. Not sure if it should also notify the ml. - will require testing
r62979@17h: jesse | 2009-05-07 11:01:09 -0400
Switch MessagingController's notifications from notifying the FML to notifying an ML
r62980@17h: jesse | 2009-05-07 11:25:22 -0400
Update AndroidManifest to know about the new world order
r62981@17h: jesse | 2009-05-07 11:26:11 -0400
Try to follow the android sdk docs for intent creation
r62982@17h: jesse | 2009-05-07 11:28:30 -0400
reset MessageList for another try at the conversion
r62983@17h: jesse | 2009-05-07 11:47:33 -0400
This version doesn't crash and has a working 'folder' layer. now to clean up the message list layer
r62984@17h: jesse | 2009-05-07 15:18:04 -0400
move step 1
r62985@17h: jesse | 2009-05-07 15:18:37 -0400
move step 1
r62986@17h: jesse | 2009-05-07 15:22:47 -0400
rename step 1
r62987@17h: jesse | 2009-05-07 17:38:02 -0400
checkpoint to move
r62988@17h: jesse | 2009-05-07 17:40:01 -0400
checkpointing a state with a working folder list and a message list that doesn't explode
r62989@17h: jesse | 2009-05-07 17:40:26 -0400
Remove debugging cruft from Welcome
r62990@17h: jesse | 2009-05-07 22:00:12 -0400
Basic functionality works.
r62991@17h: jesse | 2009-05-08 04:19:52 -0400
added a tool to build a K-9 "Beta"
r62992@17h: jesse | 2009-05-08 04:20:03 -0400
remove a disused file
r62993@17h: jesse | 2009-05-09 06:07:02 -0400
upgrading build infrastructure for the 1.5 sdk
r62994@17h: jesse | 2009-05-09 06:22:02 -0400
further refine onOpenMessage, removing more folder assumptions
r62995@17h: jesse | 2009-05-09 20:07:20 -0400
Make the Welcome activity open the autoexpandfolder rather than INBOX
r62996@17h: jesse | 2009-05-09 20:14:10 -0400
MessageList now stores the Folder name it was working with across pause-reload
r62997@17h: jesse | 2009-05-09 20:14:26 -0400
Removing dead code from FolderList
r63060@17h: jesse | 2009-05-10 00:07:33 -0400
Replace the old message list refreshing code which cleared and rebuilt the list from scratch with code which updates or deletes existing messages.
Add "go back to folder list" code
r63061@17h: jesse | 2009-05-10 00:07:50 -0400
fix message list menus for new world order
r63062@17h: jesse | 2009-05-10 00:08:11 -0400
Remove message list options from folder list menus
r63063@17h: jesse | 2009-05-10 00:10:02 -0400
remove more message list options from the folder list
r63064@17h: jesse | 2009-05-10 00:10:19 -0400
fix build.xml for the new android world order
r63065@17h: jesse | 2009-05-10 00:39:23 -0400
reformatted in advance of bug tracing
r63066@17h: jesse | 2009-05-10 05:53:28 -0400
fix our 'close' behavior to not leave extra activities around
clean up more vestigal code
r63067@17h: jesse | 2009-05-10 18:44:25 -0400
Improve "back button / accounts" workflow from FolderList -> AccountList
r63068@17h: jesse | 2009-05-10 19:11:47 -0400
* Add required code for the 'k9beta' build
r63069@17h: jesse | 2009-05-10 19:12:05 -0400
Make the folder list white backgrounded.
r63070@17h: jesse | 2009-05-10 19:12:26 -0400
* Include our required libraries in build.xml
r63071@17h: jesse | 2009-05-10 19:13:07 -0400
Added directories for our built code and our generated code
r63072@17h: jesse | 2009-05-10 19:13:36 -0400
Added a "back" button image
r63073@17h: jesse | 2009-05-10 20:13:50 -0400
Switch next/prev buttons to triangles for I18N and eventual "more easy-to-hit buttons" win
r63074@17h: jesse | 2009-05-10 20:17:18 -0400
Tidy Accounts.java for some perf hacking.
r63081@17h: jesse | 2009-05-10 22:13:33 -0400
First pass reformatting of the MessagingController
r63082@17h: jesse | 2009-05-10 23:50:28 -0400
MessageList now correctly updates when a background sync happens
r63083@17h: jesse | 2009-05-10 23:50:53 -0400
Tidying FolderList
r63084@17h: jesse | 2009-05-10 23:51:09 -0400
tidy
r63085@17h: jesse | 2009-05-10 23:51:27 -0400
tidy
r63086@17h: jesse | 2009-05-11 00:17:06 -0400
Properly update unread counts in the FolderList after sync
r63087@17h: jesse | 2009-05-11 01:38:14 -0400
Minor refactoring for readability. replace a boolean with a constant.
r63090@17h: jesse | 2009-05-11 02:58:31 -0400
now that the foreground of message lists is light, we don't need the light messagebox
r63091@17h: jesse | 2009-05-11 17:15:02 -0400
Added a string for "back to folder list"
r63092@17h: jesse | 2009-05-11 17:15:24 -0400
Added a message list header with a back button
r63093@17h: jesse | 2009-05-11 17:15:54 -0400
Remove the "folder list" button from the options menu. no sense duplicating it
r63094@17h: jesse | 2009-05-11 17:17:06 -0400
Refactored views, adding our replacement scrollable header
r63184@17h: jesse | 2009-05-12 07:07:15 -0400
fix weird bug where message lists could show a header element for a child
r63185@17h: jesse | 2009-05-12 07:08:12 -0400
Add new-style headers to folder lists. reimplement "get folder by name" to not use a bloody for loop
r63211@17h: jesse | 2009-05-12 18:37:48 -0400
Restore the former glory of the "load more messages" widget. it still needs an overhaul
r63296@17h: jesse | 2009-05-12 23:23:21 -0400
Get the indeterminate progress bar to show up again when you click "get more messages"
r63297@17h: jesse | 2009-05-13 02:40:39 -0400
Fixed off-by-one errors in click and keybindings for messagelist
r63298@17h: jesse | 2009-05-13 06:04:01 -0400
Put the folder title in the name of the folderSettings popup
r63299@17h: jesse | 2009-05-13 06:04:49 -0400
Reformatting. Removing debug logging
r63300@17h: jesse | 2009-05-13 06:05:32 -0400
Fixing "wrong item selected" bugs in the FolderList
r63328@17h: jesse | 2009-05-13 13:20:00 -0400
Update MessageView for 1.5
r63329@17h: jesse | 2009-05-13 13:50:29 -0400
A couple fixes to "picking the right item"
Titles on the message context menu
r63330@17h: jesse | 2009-05-13 13:58:37 -0400
Added an "open" context menu item to the folder list
r63347@17h: jesse | 2009-05-13 18:00:02 -0400
Try to get folderlists to sort in a stable way, so they jump around less in the ui
r63349@17h: jesse | 2009-05-13 20:37:19 -0400
Switch to using non-message-passing based notifications for redisplay of message lists, cut down redisplay frequency to not overload the display
r63432@17h: jesse | 2009-05-16 13:38:49 -0400
Android 1.5 no longer gives us apache.commons.codec by default and apache.commons.logging by default. Import them so we have em.
There's probably something smarter to do here.
r63438@17h: jesse | 2009-05-16 14:12:06 -0400
removed dead code
r63439@17h: jesse | 2009-05-16 14:30:57 -0400
Minor tidy
r63440@17h: jesse | 2009-05-16 14:39:34 -0400
First pass implementation making MessageList streamy for faster startup
r63441@17h: jesse | 2009-05-16 21:57:41 -0400
There's no reason for the FolderList to list local messages
r63442@17h: jesse | 2009-05-16 21:58:57 -0400
Switch to actually refreshing the message list after each item is loaded
r63450@17h: jesse | 2009-05-16 22:34:18 -0400
Default to pulling items out of the LocalStore by date, descending. (since that's the uneditable default ordering)
This makes our messages come out of the store in the order the user should see them
r63451@17h: jesse | 2009-05-16 22:34:44 -0400
Set some new defaults for the FolderList
r63452@17h: jesse | 2009-05-16 22:35:43 -0400
set some new message list item defaults
r63456@17h: jesse | 2009-05-17 12:56:10 -0400
It's not clear that Pop and WebDav actually set us an InternalDate. I'd rather use that so that spam doesn't topsort. But I also want this to _work_
r63457@17h: jesse | 2009-05-17 12:56:47 -0400
actually check to make sure we have a message to remove before removing it.
r63458@17h: jesse | 2009-05-17 13:10:07 -0400
Flip "security type" to before the port number, since changing security type is the thing more users are likely to know/care about and resets port number
r63469@17h: jesse | 2009-05-17 18:42:39 -0400
Provisional fix for "see the FoldeRList twice" bug
r63471@17h: jesse | 2009-05-17 20:47:41 -0400
Remove title bar from the message view
r63544@17h: jesse | 2009-05-20 23:53:38 -0400
folderlist tidying before i dig into the jumpy ordering bug
r63545@17h: jesse | 2009-05-20 23:56:00 -0400
Killing dead variables
r63546@17h: jesse | 2009-05-21 00:58:36 -0400
make the whole title section clicky
r63556@17h: jesse | 2009-05-21 01:48:13 -0400
Fix where we go when someone deletes a message
r63558@17h: jesse | 2009-05-21 22:44:46 -0400
Working toward switchable themes
r63563@17h: jesse | 2009-05-21 23:53:09 -0400
Make the MessageList's colors actually just inherit from the theme, rather than hardcoding black
r63567@17h: jesse | 2009-05-22 10:14:13 -0400
Kill a now-redundant comment
r63571@17h: jesse | 2009-05-22 19:43:30 -0400
further theme-independence work
r63572@17h: jesse | 2009-05-22 19:55:23 -0400
gete -> get (typo fix)
r63573@17h: jesse | 2009-05-22 22:48:49 -0400
First cut of a global prefs system as well as a theme preference. not that it works yet
r63577@17h: jesse | 2009-05-24 14:49:52 -0400
Once a user has actually put in valid user credentials, start syncing mail and folders in the background instantly.
This gives us a much better "new startup" experience
r63578@17h: jesse | 2009-05-24 14:55:00 -0400
MessageList doesn't need FolderUpdateWorker
r63579@17h: jesse | 2009-05-24 17:57:15 -0400
Fix "get message by uid"
Switch to showing messages 10 by 10, rather than 1 by 1 for huge loadtime performance improvements
r63587@17h: jesse | 2009-05-24 19:19:56 -0400
Cut down LocalMessage creation to not generate a MessageId or date formatter.
r63589@17h: jesse | 2009-05-24 22:22:32 -0400
Switch to null-escaping email address boundaries, rather than a VERY expensive URL-encoding
r63590@17h: jesse | 2009-05-24 22:23:21 -0400
Clean up our "auto-refresh the list when adding messages after a sync"
r63593@17h: jesse | 2009-05-24 22:53:45 -0400
replace isDateToday with a "rolling 18 hour window" variant that's more likely to give the user a useful answer and is 30x faster.
r63595@17h: jesse | 2009-05-24 23:54:14 -0400
When instantiating messges from the LocalStore, there's no need to clear headers before setting them, nor is there a need to set a generated message id
r63596@17h: jesse | 2009-05-24 23:54:39 -0400
make an overridable setGeneratedMessageId
r63597@17h: jesse | 2009-05-24 23:54:55 -0400
Remove new lies from comments
r63598@17h: jesse | 2009-05-24 23:55:35 -0400
Replace insanely expensive message header "name" part quoting with something consistent and cheap that does its work on the way INTO the database
r63605@17h: jesse | 2009-05-25 17:28:24 -0400
bring back the 1.1 sdk build.xml
r63606@17h: jesse | 2009-05-25 22:32:11 -0400
Actually enable switchable themese and compilation on 1.1
r63692@17h: jesse | 2009-05-29 23:55:17 -0400
Switch back to having titles for folder and message lists.
Restore auto-open-folder functionality
r63694@17h: jesse | 2009-05-30 18:50:39 -0400
Remove several off-by-one errors introduced by yesterday's return to android titlebars
r63696@17h: jesse | 2009-05-30 23:45:03 -0400
use convertView properly for performance and memory imrpovement in FolderList and MessageList
r63698@17h: jesse | 2009-05-31 19:42:59 -0400
Switch to using background shading to indicate "not yet fetched"
r63701@17h: jesse | 2009-05-31 21:28:47 -0400
Remving code we don't actually need these bits of apache commons on 1.1
2009-05-31 21:35:05 -04:00
}
2009-12-06 23:46:42 -05:00
2009-11-24 19:40:29 -05:00
/ *
* For performance reasons , we add headers instead of setting them ( see super implementation )
* which removes ( expensive ) them before adding them
* /
@Override
2011-02-06 17:09:48 -05:00
public void setRecipients ( RecipientType type , Address [ ] addresses ) throws MessagingException {
if ( type = = RecipientType . TO ) {
if ( addresses = = null | | addresses . length = = 0 ) {
2009-11-24 19:40:29 -05:00
this . mTo = null ;
2011-02-06 17:09:48 -05:00
} else {
2009-11-24 19:40:29 -05:00
this . mTo = addresses ;
}
2011-02-06 17:09:48 -05:00
} else if ( type = = RecipientType . CC ) {
if ( addresses = = null | | addresses . length = = 0 ) {
2009-11-24 19:40:29 -05:00
this . mCc = null ;
2011-02-06 17:09:48 -05:00
} else {
2009-11-24 19:40:29 -05:00
this . mCc = addresses ;
}
2011-02-06 17:09:48 -05:00
} else if ( type = = RecipientType . BCC ) {
if ( addresses = = null | | addresses . length = = 0 ) {
2009-11-24 19:40:29 -05:00
this . mBcc = null ;
2011-02-06 17:09:48 -05:00
} else {
2009-11-24 19:40:29 -05:00
this . mBcc = addresses ;
}
2011-02-06 17:09:48 -05:00
} else {
2009-11-24 19:40:29 -05:00
throw new MessagingException ( " Unrecognized recipient type. " ) ;
}
2009-12-06 23:46:42 -05:00
mMessageDirty = true ;
2009-06-26 02:55:32 -04:00
}
2011-02-06 17:09:48 -05:00
public void setFlagInternal ( Flag flag , boolean set ) throws MessagingException {
2008-11-01 17:32:06 -04:00
super . setFlag ( flag , set ) ;
}
2012-12-07 09:50:55 -05:00
@Override
2011-02-06 17:09:48 -05:00
public long getId ( ) {
2008-11-01 17:32:06 -04:00
return mId ;
}
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setFlag ( final Flag flag , final boolean set ) throws MessagingException {
2010-08-29 12:57:31 -04:00
2011-02-06 17:09:48 -05:00
try {
database . execute ( true , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2011-02-06 17:09:48 -05:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException , UnavailableStorageException {
try {
if ( flag = = Flag . DELETED & & set ) {
2010-11-13 16:40:56 -05:00
delete ( ) ;
}
2008-11-01 17:32:06 -04:00
2010-11-13 16:40:56 -05:00
LocalMessage . super . setFlag ( flag , set ) ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
/ *
* Set the flags on the message .
* /
2012-12-03 23:13:58 -05:00
ContentValues cv = new ContentValues ( ) ;
cv . put ( " flags " , serializeFlags ( getFlags ( ) ) ) ;
cv . put ( " read " , isSet ( Flag . SEEN ) ? 1 : 0 ) ;
cv . put ( " flagged " , isSet ( Flag . FLAGGED ) ? 1 : 0 ) ;
cv . put ( " answered " , isSet ( Flag . ANSWERED ) ? 1 : 0 ) ;
cv . put ( " forwarded " , isSet ( Flag . FORWARDED ) ? 1 : 0 ) ;
db . update ( " messages " , cv , " id = ? " , new String [ ] { Long . toString ( mId ) } ) ;
2010-11-13 16:40:56 -05:00
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2010-11-13 16:40:56 -05:00
}
2010-08-29 12:56:54 -04:00
2012-10-23 18:08:44 -04:00
notifyChange ( ) ;
2010-08-29 12:56:54 -04:00
}
2010-11-12 20:47:08 -05:00
/ *
* If a message is being marked as deleted we want to clear out it ' s content
* and attachments as well . Delete will not actually remove the row since we need
* to retain the uid for synchronization purposes .
* /
2010-08-29 12:56:54 -04:00
private void delete ( ) throws MessagingException
{
/ *
* Delete all of the message ' s content to save space .
* /
2011-02-06 17:09:48 -05:00
try {
database . execute ( true , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
2012-10-08 16:51:29 -04:00
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException ,
UnavailableStorageException {
String [ ] idArg = new String [ ] { Long . toString ( mId ) } ;
ContentValues cv = new ContentValues ( ) ;
cv . put ( " deleted " , 1 ) ;
cv . put ( " empty " , 1 ) ;
cv . putNull ( " subject " ) ;
cv . putNull ( " sender_list " ) ;
cv . putNull ( " date " ) ;
cv . putNull ( " to_list " ) ;
cv . putNull ( " cc_list " ) ;
cv . putNull ( " bcc_list " ) ;
cv . putNull ( " preview " ) ;
cv . putNull ( " html_content " ) ;
cv . putNull ( " text_content " ) ;
cv . putNull ( " reply_to_list " ) ;
db . update ( " messages " , cv , " id = ? " , idArg ) ;
2010-11-13 16:40:56 -05:00
/ *
* Delete all of the message ' s attachments to save space .
* We do this explicit deletion here because we ' re not deleting the record
* in messages , which means our ON DELETE trigger for messages won ' t cascade
* /
2011-02-06 17:09:48 -05:00
try {
2010-11-13 16:40:56 -05:00
( ( LocalFolder ) mFolder ) . deleteAttachments ( mId ) ;
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
2012-10-08 16:51:29 -04:00
db . delete ( " attachments " , " message_id = ? " , idArg ) ;
2010-11-13 16:40:56 -05:00
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2010-11-13 16:40:56 -05:00
}
2010-08-29 12:56:54 -04:00
( ( LocalFolder ) mFolder ) . deleteHeaders ( mId ) ;
2012-10-23 18:08:44 -04:00
notifyChange ( ) ;
2008-11-01 17:32:06 -04:00
}
2009-12-14 21:51:18 -05:00
2010-11-12 16:38:02 -05:00
/ *
* Completely remove a message from the local database
2012-10-08 16:51:29 -04:00
*
* TODO : document how this updates the thread structure
2010-11-12 16:38:02 -05:00
* /
@Override
2011-02-06 17:09:48 -05:00
public void destroy ( ) throws MessagingException {
try {
database . execute ( true , new DbCallback < Void > ( ) {
2010-11-13 16:40:56 -05:00
@Override
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException ,
2011-02-06 17:09:48 -05:00
UnavailableStorageException {
try {
2012-10-08 16:51:29 -04:00
LocalFolder localFolder = ( LocalFolder ) mFolder ;
localFolder . deleteAttachments ( mId ) ;
2013-01-10 21:40:35 -05:00
if ( hasThreadChildren ( db , mId ) ) {
// This message has children in the thread structure so we need to
// make it an empty message.
ContentValues cv = new ContentValues ( ) ;
cv . put ( " id " , mId ) ;
cv . put ( " folder_id " , localFolder . getId ( ) ) ;
cv . put ( " deleted " , 0 ) ;
cv . put ( " message_id " , getMessageId ( ) ) ;
cv . put ( " empty " , 1 ) ;
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
db . replace ( " messages " , null , cv ) ;
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
// Nothing else to do
return null ;
2012-10-08 16:51:29 -04:00
}
2013-01-10 21:40:35 -05:00
// Get the message ID of the parent message if it's empty
long currentId = getEmptyThreadParent ( db , mId ) ;
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
// Delete the placeholder message
deleteMessageRow ( db , mId ) ;
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
/ *
* Walk the thread tree to delete all empty parents without children
* /
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
while ( currentId ! = - 1 ) {
if ( hasThreadChildren ( db , currentId ) ) {
// We made sure there are no empty leaf nodes and can stop now.
break ;
2012-10-08 16:51:29 -04:00
}
2013-01-10 21:40:35 -05:00
// Get ID of the (empty) parent for the next iteration
long newId = getEmptyThreadParent ( db , currentId ) ;
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
// Delete the empty message
deleteMessageRow ( db , currentId ) ;
currentId = newId ;
2012-10-08 16:51:29 -04:00
}
2011-02-06 17:09:48 -05:00
} catch ( MessagingException e ) {
2010-11-13 16:40:56 -05:00
throw new WrappedException ( e ) ;
}
return null ;
}
} ) ;
2011-02-06 17:09:48 -05:00
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
2010-11-13 16:40:56 -05:00
}
2012-10-23 18:08:44 -04:00
notifyChange ( ) ;
2010-11-12 16:38:02 -05:00
}
2010-08-29 12:56:54 -04:00
2013-01-10 21:40:35 -05:00
/ * *
* Get ID of the the given message ' s parent if the parent is an empty message .
*
* @param db
* { @link SQLiteDatabase } instance to access the database .
* @param messageId
* The database ID of the message to get the parent for .
*
* @return Message ID of the parent message if there exists a parent and it is empty .
* Otherwise { @code - 1 } .
* /
private long getEmptyThreadParent ( SQLiteDatabase db , long messageId ) {
Cursor cursor = db . rawQuery (
" SELECT m.id " +
" FROM threads t1 " +
" JOIN threads t2 ON (t1.parent = t2.id) " +
2013-01-11 22:21:53 -05:00
" LEFT JOIN messages m ON (t2.message_id = m.id) " +
2013-01-10 21:40:35 -05:00
" WHERE t1.message_id = ? AND m.empty = 1 " ,
new String [ ] { Long . toString ( messageId ) } ) ;
try {
return ( cursor . moveToFirst ( ) & & ! cursor . isNull ( 0 ) ) ? cursor . getLong ( 0 ) : - 1 ;
} finally {
cursor . close ( ) ;
}
}
/ * *
* Check whether or not a message has child messages in the thread structure .
*
* @param db
* { @link SQLiteDatabase } instance to access the database .
* @param messageId
* The database ID of the message to get the children for .
*
* @return { @code true } if the message has children . { @code false } otherwise .
* /
private boolean hasThreadChildren ( SQLiteDatabase db , long messageId ) {
Cursor cursor = db . rawQuery (
" SELECT COUNT(t2.id) " +
" FROM threads t1 " +
" JOIN threads t2 ON (t2.parent = t1.id) " +
" WHERE t1.message_id = ? " ,
new String [ ] { Long . toString ( messageId ) } ) ;
try {
return ( cursor . moveToFirst ( ) & & ! cursor . isNull ( 0 ) & & cursor . getLong ( 0 ) > 0L ) ;
} finally {
cursor . close ( ) ;
}
}
/ * *
* Delete a message from the ' messages ' and ' threads ' tables .
*
* @param db
* { @link SQLiteDatabase } instance to access the database .
* @param messageId
* The database ID of the message to delete .
* /
private void deleteMessageRow ( SQLiteDatabase db , long messageId ) {
String [ ] idArg = { Long . toString ( messageId ) } ;
// Delete the message
db . delete ( " messages " , " id = ? " , idArg ) ;
// Delete row in 'threads' table
// TODO: create trigger for 'messages' table to get rid of the row in 'threads' table
db . delete ( " threads " , " message_id = ? " , idArg ) ;
}
2011-02-06 17:09:48 -05:00
private void loadHeaders ( ) throws UnavailableStorageException {
2009-12-20 00:41:43 -05:00
ArrayList < LocalMessage > messages = new ArrayList < LocalMessage > ( ) ;
messages . add ( this ) ;
mHeadersLoaded = true ; // set true before calling populate headers to stop recursion
( ( LocalFolder ) mFolder ) . populateHeaders ( messages ) ;
2009-12-14 21:51:18 -05:00
2009-12-20 00:41:43 -05:00
}
2009-12-14 21:51:18 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void addHeader ( String name , String value ) throws UnavailableStorageException {
2009-12-20 00:41:43 -05:00
if ( ! mHeadersLoaded )
loadHeaders ( ) ;
super . addHeader ( name , value ) ;
2009-12-14 21:51:18 -05:00
}
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void setHeader ( String name , String value ) throws UnavailableStorageException {
2009-12-20 00:41:43 -05:00
if ( ! mHeadersLoaded )
loadHeaders ( ) ;
super . setHeader ( name , value ) ;
}
2009-12-14 21:51:18 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public String [ ] getHeader ( String name ) throws UnavailableStorageException {
2009-12-20 00:41:43 -05:00
if ( ! mHeadersLoaded )
loadHeaders ( ) ;
return super . getHeader ( name ) ;
}
2009-12-14 21:51:18 -05:00
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public void removeHeader ( String name ) throws UnavailableStorageException {
2009-12-20 00:41:43 -05:00
if ( ! mHeadersLoaded )
loadHeaders ( ) ;
super . removeHeader ( name ) ;
}
2009-12-14 21:51:18 -05:00
2010-05-21 11:34:29 -04:00
@Override
2011-02-06 17:09:48 -05:00
public Set < String > getHeaderNames ( ) throws UnavailableStorageException {
2010-05-21 11:34:29 -04:00
if ( ! mHeadersLoaded )
loadHeaders ( ) ;
return super . getHeaderNames ( ) ;
}
2012-01-03 20:27:51 -05:00
@Override
public LocalMessage clone ( ) {
LocalMessage message = new LocalMessage ( ) ;
super . copy ( message ) ;
message . mId = mId ;
message . mAttachmentCount = mAttachmentCount ;
message . mSubject = mSubject ;
message . mPreview = mPreview ;
message . mHeadersLoaded = mHeadersLoaded ;
message . mMessageDirty = mMessageDirty ;
return message ;
}
2012-10-08 16:51:29 -04:00
2013-01-10 21:40:35 -05:00
public long getThreadId ( ) {
return mThreadId ;
2012-10-08 16:51:29 -04:00
}
2013-01-10 21:40:35 -05:00
public long getRootId ( ) {
return mRootId ;
2012-10-08 16:51:29 -04:00
}
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public static class LocalAttachmentBodyPart extends MimeBodyPart {
2008-11-01 17:32:06 -04:00
private long mAttachmentId = - 1 ;
2011-02-06 17:09:48 -05:00
public LocalAttachmentBodyPart ( Body body , long attachmentId ) throws MessagingException {
2008-11-01 17:32:06 -04:00
super ( body ) ;
mAttachmentId = attachmentId ;
}
/ * *
* Returns the local attachment id of this body , or - 1 if it is not stored .
* @return
* /
2011-02-06 17:09:48 -05:00
public long getAttachmentId ( ) {
2008-11-01 17:32:06 -04:00
return mAttachmentId ;
}
2011-02-06 17:09:48 -05:00
public void setAttachmentId ( long attachmentId ) {
2008-11-01 17:32:06 -04:00
mAttachmentId = attachmentId ;
}
2010-04-16 08:20:10 -04:00
@Override
2011-02-06 17:09:48 -05:00
public String toString ( ) {
2008-11-01 17:32:06 -04:00
return " " + mAttachmentId ;
}
}
2011-02-06 17:09:48 -05:00
public static class LocalAttachmentBody implements Body {
2010-08-02 07:55:31 -04:00
private static final byte [ ] EMPTY_BYTE_ARRAY = new byte [ 0 ] ;
2008-11-01 17:32:06 -04:00
private Application mApplication ;
private Uri mUri ;
2011-02-06 17:09:48 -05:00
public LocalAttachmentBody ( Uri uri , Application application ) {
2008-11-01 17:32:06 -04:00
mApplication = application ;
mUri = uri ;
}
2012-12-07 09:50:55 -05:00
@Override
2011-02-06 17:09:48 -05:00
public InputStream getInputStream ( ) throws MessagingException {
try {
2008-11-01 17:32:06 -04:00
return mApplication . getContentResolver ( ) . openInputStream ( mUri ) ;
2011-02-06 17:09:48 -05:00
} catch ( FileNotFoundException fnfe ) {
2010-02-17 22:28:31 -05:00
/ *
* Since it ' s completely normal for us to try to serve up attachments that
* have been blown away , we just return an empty stream .
* /
2010-08-02 07:55:31 -04:00
return new ByteArrayInputStream ( EMPTY_BYTE_ARRAY ) ;
2010-02-17 22:28:31 -05:00
}
2008-11-01 17:32:06 -04:00
}
2012-12-07 09:50:55 -05:00
@Override
2011-02-06 17:09:48 -05:00
public void writeTo ( OutputStream out ) throws IOException , MessagingException {
2008-11-01 17:32:06 -04:00
InputStream in = getInputStream ( ) ;
Base64OutputStream base64Out = new Base64OutputStream ( out ) ;
2011-12-31 12:38:41 -05:00
try {
IOUtils . copy ( in , base64Out ) ;
} finally {
base64Out . close ( ) ;
}
2008-11-01 17:32:06 -04:00
}
2011-02-06 17:09:48 -05:00
public Uri getContentUri ( ) {
2008-11-01 17:32:06 -04:00
return mUri ;
}
}
2012-10-08 16:51:29 -04:00
static class ThreadInfo {
2013-01-10 21:40:35 -05:00
public final long threadId ;
public final long msgId ;
2012-10-08 16:51:29 -04:00
public final String messageId ;
public final long rootId ;
public final long parentId ;
2013-01-10 21:40:35 -05:00
public ThreadInfo ( long threadId , long msgId , String messageId , long rootId , long parentId ) {
this . threadId = threadId ;
this . msgId = msgId ;
2012-10-08 16:51:29 -04:00
this . messageId = messageId ;
this . rootId = rootId ;
this . parentId = parentId ;
}
}
2012-10-16 09:46:40 -04:00
public LockableDatabase getDatabase ( ) {
return database ;
}
2012-10-23 18:08:44 -04:00
private void notifyChange ( ) {
Uri uri = Uri . withAppendedPath ( EmailProvider . CONTENT_URI , " account/ " + uUid + " /messages " ) ;
mContentResolver . notifyChange ( uri , null ) ;
}
2012-12-05 23:25:41 -05:00
/ * *
* Split database operations with a large set of arguments into multiple SQL statements .
*
* < p >
* At the time of this writing ( 2012 - 12 - 06 ) SQLite only supports around 1000 arguments . That ' s
* why we have to split SQL statements with a large set of arguments into multiple SQL
* statements each working on a subset of the arguments .
* < / p >
*
* @param selectionCallback
* Supplies the argument set and the code to query / update the database .
* @param batchSize
* The maximum size of the selection set in each SQL statement .
*
* @throws MessagingException
* /
public void doBatchSetSelection ( final BatchSetSelection selectionCallback , final int batchSize )
throws MessagingException {
final List < String > selectionArgs = new ArrayList < String > ( ) ;
int start = 0 ;
while ( start < selectionCallback . getListSize ( ) ) {
final StringBuilder selection = new StringBuilder ( ) ;
selection . append ( " IN ( " ) ;
int count = Math . min ( selectionCallback . getListSize ( ) - start , batchSize ) ;
for ( int i = start , end = start + count ; i < end ; i + + ) {
if ( i > start ) {
selection . append ( " ,? " ) ;
} else {
selection . append ( " ? " ) ;
}
selectionArgs . add ( selectionCallback . getListItem ( i ) ) ;
}
selection . append ( " ) " ) ;
try {
database . execute ( true , new DbCallback < Void > ( ) {
@Override
public Void doDbWork ( final SQLiteDatabase db ) throws WrappedException ,
UnavailableStorageException {
selectionCallback . doDbWork ( db , selection . toString ( ) ,
selectionArgs . toArray ( EMPTY_STRING_ARRAY ) ) ;
return null ;
}
} ) ;
selectionCallback . postDbWork ( ) ;
} catch ( WrappedException e ) {
throw ( MessagingException ) e . getCause ( ) ;
}
selectionArgs . clear ( ) ;
start + = count ;
}
}
/ * *
* Defines the behavior of { @link LocalStore # doBatchSetSelection ( BatchSetSelection , int ) } .
* /
public interface BatchSetSelection {
/ * *
* @return The size of the argument list .
* /
int getListSize ( ) ;
/ * *
* Get a specific item of the argument list .
*
* @param index
* The index of the item .
*
* @return Item at position { @code i } of the argument list .
* /
String getListItem ( int index ) ;
/ * *
* Execute the SQL statement .
*
* @param db
* Use this { @link SQLiteDatabase } instance for your SQL statement .
* @param selectionSet
* A partial selection string containing place holders for the argument list , e . g .
* { @code " IN (?,?,?) " } ( starts with a space ) .
* @param selectionArgs
* The current subset of the argument list .
* @throws UnavailableStorageException
* /
void doDbWork ( SQLiteDatabase db , String selectionSet , String [ ] selectionArgs )
throws UnavailableStorageException ;
/ * *
* This will be executed after each invocation of
* { @link # doDbWork ( SQLiteDatabase , String , String [ ] ) } ( after the transaction has been
* committed ) .
* /
void postDbWork ( ) ;
}
/ * *
* Change the state of a flag for a list of messages .
*
* < p >
* The goal of this method is to be fast . Currently this means using as few SQL UPDATE
2013-01-11 20:28:12 -05:00
* statements as possible .
2012-12-05 23:25:41 -05:00
*
* @param messageIds
* A list of primary keys in the " messages " table .
* @param flag
* The flag to change . This must be a flag with a separate column in the database .
* @param newState
* { @code true } , if the flag should be set . { @code false } , otherwise .
*
* @throws MessagingException
* /
2013-01-10 21:40:35 -05:00
public void setFlag ( final List < Long > messageIds , final Flag flag , final boolean newState )
throws MessagingException {
2012-12-05 23:25:41 -05:00
final ContentValues cv = new ContentValues ( ) ;
2013-02-18 22:45:14 -05:00
cv . put ( getColumnNameForFlag ( flag ) , newState ) ;
2012-12-05 23:25:41 -05:00
doBatchSetSelection ( new BatchSetSelection ( ) {
@Override
public int getListSize ( ) {
return messageIds . size ( ) ;
}
@Override
public String getListItem ( int index ) {
return Long . toString ( messageIds . get ( index ) ) ;
}
@Override
public void doDbWork ( SQLiteDatabase db , String selectionSet , String [ ] selectionArgs )
throws UnavailableStorageException {
db . update ( " messages " , cv , " (empty IS NULL OR empty != 1) AND id " + selectionSet ,
selectionArgs ) ;
}
@Override
public void postDbWork ( ) {
notifyChange ( ) ;
}
} , FLAG_UPDATE_BATCH_SIZE ) ;
}
2013-01-11 20:28:12 -05:00
/ * *
* Change the state of a flag for a list of threads .
*
* < p >
* The goal of this method is to be fast . Currently this means using as few SQL UPDATE
* statements as possible .
*
* @param threadRootIds
* A list of root thread IDs .
* @param flag
* The flag to change . This must be a flag with a separate column in the database .
* @param newState
* { @code true } , if the flag should be set . { @code false } , otherwise .
*
* @throws MessagingException
* /
public void setFlagForThreads ( final List < Long > threadRootIds , Flag flag , final boolean newState )
2013-01-10 21:40:35 -05:00
throws MessagingException {
2013-02-18 22:45:14 -05:00
final String flagColumn = getColumnNameForFlag ( flag ) ;
2013-01-10 21:40:35 -05:00
doBatchSetSelection ( new BatchSetSelection ( ) {
@Override
public int getListSize ( ) {
2013-01-11 20:28:12 -05:00
return threadRootIds . size ( ) ;
2013-01-10 21:40:35 -05:00
}
@Override
public String getListItem ( int index ) {
2013-01-11 20:28:12 -05:00
return Long . toString ( threadRootIds . get ( index ) ) ;
2013-01-10 21:40:35 -05:00
}
@Override
public void doDbWork ( SQLiteDatabase db , String selectionSet , String [ ] selectionArgs )
throws UnavailableStorageException {
db . execSQL ( " UPDATE messages SET " + flagColumn + " = " + ( ( newState ) ? " 1 " : " 0 " ) +
" WHERE id IN ( " +
2013-01-11 20:28:12 -05:00
" SELECT m.id FROM threads t " +
" LEFT JOIN messages m ON (t.message_id = m.id) " +
2013-01-10 21:40:35 -05:00
" WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
2013-03-07 19:15:26 -05:00
" AND t.root " + selectionSet + " ) " ,
selectionArgs ) ;
2012-12-05 23:25:41 -05:00
}
@Override
public void postDbWork ( ) {
notifyChange ( ) ;
}
2013-01-11 20:28:12 -05:00
} , THREAD_FLAG_UPDATE_BATCH_SIZE ) ;
2012-12-05 23:25:41 -05:00
}
/ * *
* Get folder name and UID for the supplied messages .
*
* @param messageIds
* A list of primary keys in the " messages " table .
* @param threadedList
2013-01-11 22:02:21 -05:00
* If this is { @code true } , { @code messageIds } contains the thread IDs of the messages
* at the root of a thread . In that case return UIDs for all messages in these threads .
2012-12-05 23:25:41 -05:00
* If this is { @code false } only the UIDs for messages in { @code messageIds } are
* returned .
*
* @return The list of UIDs for the messages grouped by folder name .
*
* @throws MessagingException
* /
public Map < String , List < String > > getFoldersAndUids ( final List < Long > messageIds ,
final boolean threadedList ) throws MessagingException {
final Map < String , List < String > > folderMap = new HashMap < String , List < String > > ( ) ;
doBatchSetSelection ( new BatchSetSelection ( ) {
@Override
public int getListSize ( ) {
return messageIds . size ( ) ;
}
@Override
public String getListItem ( int index ) {
return Long . toString ( messageIds . get ( index ) ) ;
}
@Override
public void doDbWork ( SQLiteDatabase db , String selectionSet , String [ ] selectionArgs )
throws UnavailableStorageException {
if ( threadedList ) {
2013-01-10 21:40:35 -05:00
String sql = " SELECT m.uid, f.name " +
2013-01-11 22:02:21 -05:00
" FROM threads t " +
" LEFT JOIN messages m ON (t.message_id = m.id) " +
" LEFT JOIN folders f ON (m.folder_id = f.id) " +
2013-01-10 21:40:35 -05:00
" WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
2013-03-07 19:15:26 -05:00
" AND t.root " + selectionSet ;
2012-12-05 23:25:41 -05:00
2013-03-07 19:15:26 -05:00
getDataFromCursor ( db . rawQuery ( sql , selectionArgs ) ) ;
2013-01-10 21:40:35 -05:00
} else {
2013-01-11 22:02:21 -05:00
String sql =
2013-01-10 21:40:35 -05:00
" SELECT m.uid, f.name " +
" FROM messages m " +
2013-01-11 22:02:21 -05:00
" LEFT JOIN folders f ON (m.folder_id = f.id) " +
" WHERE (m.empty IS NULL OR m.empty != 1) AND m.id " + selectionSet ;
2013-01-10 21:40:35 -05:00
getDataFromCursor ( db . rawQuery ( sql , selectionArgs ) ) ;
2012-12-05 23:25:41 -05:00
}
}
private void getDataFromCursor ( Cursor cursor ) {
try {
while ( cursor . moveToNext ( ) ) {
String uid = cursor . getString ( 0 ) ;
String folderName = cursor . getString ( 1 ) ;
List < String > uidList = folderMap . get ( folderName ) ;
if ( uidList = = null ) {
uidList = new ArrayList < String > ( ) ;
folderMap . put ( folderName , uidList ) ;
}
uidList . add ( uid ) ;
}
} finally {
cursor . close ( ) ;
}
}
@Override
public void postDbWork ( ) {
notifyChange ( ) ;
}
} , UID_CHECK_BATCH_SIZE ) ;
return folderMap ;
}
2008-11-01 17:32:06 -04:00
}