1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 11:42:16 -05:00

Issue 131

Provide two options for each Account:
Compact (which VACUUMs the accounts and prunes recreatable
attachments)

Clear all data (danger!) (which wipes all messages and attachments in
the account, except placeholder deleted messages)

Displays a Toast when the Compact or Clear is queued, and another
Toast with the shrinkage results.

(Also, a small change to increase probability of seeing messages after
a sync, by setting the needsRefresh on all folders when a sync is done.)
This commit is contained in:
Daniel Applebaum 2009-02-10 03:18:42 +00:00
parent 8c466c35ba
commit 3e4843e756
10 changed files with 417 additions and 44 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -6,8 +6,13 @@
android:title="@string/empty_trash_action" /> android:title="@string/empty_trash_action" />
<item android:id="@+id/edit_account" <item android:id="@+id/edit_account"
android:title="@string/account_settings_action" /> android:title="@string/account_settings_action" />
<item android:id="@+id/compact"
android:title="@string/compact_action" />
<item android:id="@+id/delete_account" <item android:id="@+id/delete_account"
android:title="@string/remove_account_action" /> android:title="@string/remove_account_action" />
<item android:id="@+id/clear_pending" <item android:id="@+id/clear_pending"
android:title="@string/clear_pending_action" /> android:title="@string/clear_pending_action" />
<item android:id="@+id/clear"
android:title="@string/clear_action" />
</menu> </menu>

View File

@ -65,6 +65,16 @@
android:title="@string/refresh_action" android:title="@string/refresh_action"
android:icon="@drawable/ic_menu_refresh" android:icon="@drawable/ic_menu_refresh"
/> />
<item
android:id="@+id/compact"
android:title="@string/compact_action"
android:icon="@drawable/ic_menu_compact"
/>
<item
android:id="@+id/clear"
android:title="@string/clear_action"
android:icon="@drawable/ic_menu_clear"
/>
</menu> </menu>

View File

@ -78,6 +78,21 @@
<string name="load_more_messages_fmt">Load up <string name="load_more_messages_fmt">Load up
to <xliff:g id="messages_to_load">%d</xliff:g> more</string> to <xliff:g id="messages_to_load">%d</xliff:g> more</string>
<string name="abbrev_gigabytes">GB</string>
<string name="abbrev_megabytes">MB</string>
<string name="abbrev_kilobytes">KB</string>
<string name="abbrev_bytes">B</string>
<string name="account_size_changed">
Account \"<xliff:g id="account">%s</xliff:g>\" shrunk from
<xliff:g id="oldSize">%s</xliff:g>
to
<xliff:g id="newSize">%s</xliff:g>
</string>
<string name="compacting_account">Compacting account \"<xliff:g id="account">%s</xliff:g>\"</string>
<string name="clearing_account">Clearing account \"<xliff:g id="account">%s</xliff:g>\"</string>
<string name="notification_new_title">New email</string> <string name="notification_new_title">New email</string>
<string name="notification_new_scrolling">New email from <xliff:g id="sender">%s</xliff:g></string> <string name="notification_new_scrolling">New email from <xliff:g id="sender">%s</xliff:g></string>
@ -254,6 +269,9 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
<string name="account_setup_outgoing_authentication_webdav_before_smtp_label">WebDav(Exchange) before SMTP</string> <string name="account_setup_outgoing_authentication_webdav_before_smtp_label">WebDav(Exchange) before SMTP</string>
<string name="account_setup_options_title">Account options</string> <string name="account_setup_options_title">Account options</string>
<string name="compact_action">Compact</string>
<string name="clear_action">Clear all data (danger!)</string>
<string name="account_setup_options_mail_check_frequency_label">Email checking frequency</string> <string name="account_setup_options_mail_check_frequency_label">Email checking frequency</string>
<!-- Frequency also used in account_settings_* --> <!-- Frequency also used in account_settings_* -->

View File

@ -2487,7 +2487,65 @@ s * critical data as fast as possible, and then we'll fill in the de
} }
}); });
} }
public void compact(final Account account, final MessagingListener ml)
{
putBackground("compact:" + account.getDescription(), ml, new Runnable()
{
public void run()
{
try
{
LocalStore localStore = (LocalStore)Store.getInstance(account.getLocalStoreUri(), mApplication);
long oldSize = localStore.getSize();
localStore.compact();
long newSize = localStore.getSize();
if (ml != null)
{
ml.accountSizeChanged(account, oldSize, newSize);
}
for (MessagingListener l : getListeners()) {
l.accountSizeChanged(account, oldSize, newSize);
l.accountReset(account);
}
}
catch (Exception e)
{
Log.e(Email.LOG_TAG, "Failed to compact account " + account.getDescription(), e);
}
}
});
}
public void clear(final Account account, final MessagingListener ml)
{
putBackground("clear:" + account.getDescription(), ml, new Runnable()
{
public void run()
{
try
{
LocalStore localStore = (LocalStore)Store.getInstance(account.getLocalStoreUri(), mApplication);
long oldSize = localStore.getSize();
localStore.clear();
localStore.resetVisibleLimits(account.getDisplayCount());
long newSize = localStore.getSize();
if (ml != null)
{
ml.accountSizeChanged(account, oldSize, newSize);
}
for (MessagingListener l : getListeners()) {
l.accountSizeChanged(account, oldSize, newSize);
l.accountReset(account);
}
}
catch (Exception e)
{
Log.e(Email.LOG_TAG, "Failed to compact account " + account.getDescription(), e);
}
}
});
}
public void saveDraft(final Account account, final Message message) { public void saveDraft(final Account account, final Message message) {
try { try {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication); Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);

View File

@ -18,6 +18,14 @@ public class MessagingListener {
public void accountStatusChanged(Account account, int unreadMessageCount) { public void accountStatusChanged(Account account, int unreadMessageCount) {
} }
public void accountSizeChanged(Account account, long oldSize, long newSize)
{
}
public void accountReset(Account account) {
}
public void listFoldersStarted(Account account) { public void listFoldersStarted(Account account) {
} }

View File

@ -29,6 +29,7 @@ import android.widget.ArrayAdapter;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
@ -64,9 +65,17 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
private AccountsHandler mHandler = new AccountsHandler(); private AccountsHandler mHandler = new AccountsHandler();
private AccountsAdapter mAdapter; private AccountsAdapter mAdapter;
private class AccountSizeChangedHolder {
Account account;
long oldSize;
long newSize;
}
class AccountsHandler extends Handler class AccountsHandler extends Handler
{ {
private static final int DATA_CHANGED = 1; private static final int DATA_CHANGED = 1;
private static final int MSG_ACCOUNT_SIZE_CHANGED = 2;
private static final int MSG_WORKING_ACCOUNT = 3;
public void handleMessage(android.os.Message msg) public void handleMessage(android.os.Message msg)
{ {
@ -78,6 +87,29 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
} }
break; break;
case MSG_WORKING_ACCOUNT:
{
Account account = (Account)msg.obj;
int res = msg.arg1;
String toastText = getString(res, account.getDescription());
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT);
toast.show();
break;
}
case MSG_ACCOUNT_SIZE_CHANGED:
{
AccountSizeChangedHolder holder = (AccountSizeChangedHolder)msg.obj;
Account account = holder.account;
Long oldSize = holder.oldSize;
Long newSize = holder.newSize;
String toastText = getString(R.string.account_size_changed, account.getDescription(),
SizeFormatter.formatSize(getApplication(), oldSize), SizeFormatter.formatSize(getApplication(), newSize));;
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG);
toast.show();
break;
}
default: default:
super.handleMessage(msg); super.handleMessage(msg);
} }
@ -87,6 +119,28 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
{ {
sendEmptyMessage(DATA_CHANGED); sendEmptyMessage(DATA_CHANGED);
} }
public void workingAccount(Account account, int res)
{
android.os.Message msg = new android.os.Message();
msg.what = MSG_WORKING_ACCOUNT;
msg.obj = account;
msg.arg1 = res;
sendMessage(msg);
}
public void accountSizeChanged(Account account, long oldSize, long newSize)
{
android.os.Message msg = new android.os.Message();
msg.what = MSG_ACCOUNT_SIZE_CHANGED;
AccountSizeChangedHolder holder = new AccountSizeChangedHolder();
holder.account = account;
holder.oldSize = oldSize;
holder.newSize = newSize;
msg.obj = holder;
sendMessage(msg);
}
} }
@ -98,6 +152,14 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
mHandler.dataChanged(); mHandler.dataChanged();
} }
@Override
public void accountSizeChanged(Account account, long oldSize, long newSize)
{
mHandler.accountSizeChanged(account, oldSize, newSize);
}
@Override @Override
public void synchronizeMailboxFinished( public void synchronizeMailboxFinished(
Account account, Account account,
@ -275,9 +337,28 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
case R.id.empty_trash: case R.id.empty_trash:
onEmptyTrash(account); onEmptyTrash(account);
break; break;
case R.id.compact:
onCompact(account);
break;
case R.id.clear:
onClear(account);
break;
} }
return true; return true;
} }
private void onCompact(Account account)
{
mHandler.workingAccount(account, R.string.compacting_account);
MessagingController.getInstance(getApplication()).compact(account, null);
}
private void onClear(Account account)
{
mHandler.workingAccount(account, R.string.clearing_account);
MessagingController.getInstance(getApplication()).clear(account, null);
}
public void onItemClick(AdapterView parent, View view, int position, long id) { public void onItemClick(AdapterView parent, View view, int position, long id) {
Account account = (Account)parent.getItemAtPosition(position); Account account = (Account)parent.getItemAtPosition(position);

View File

@ -191,6 +191,8 @@ public class FolderMessageList extends ExpandableListActivity
dateFormat = null; dateFormat = null;
timeFormat = null; timeFormat = null;
} }
class FolderMessageListHandler extends Handler class FolderMessageListHandler extends Handler
{ {
@ -211,6 +213,10 @@ public class FolderMessageList extends ExpandableListActivity
private static final int MSG_FOLDER_SYNCING = 18; private static final int MSG_FOLDER_SYNCING = 18;
private static final int MSG_SENDING_OUTBOX = 19; private static final int MSG_SENDING_OUTBOX = 19;
private static final int MSG_ACCOUNT_SIZE_CHANGED = 20;
private static final int MSG_WORKING_ACCOUNT = 21;
@Override @Override
public void handleMessage(android.os.Message msg) public void handleMessage(android.os.Message msg)
{ {
@ -249,6 +255,27 @@ public class FolderMessageList extends ExpandableListActivity
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
break; break;
} }
case MSG_ACCOUNT_SIZE_CHANGED:
{
Long[] sizes = (Long[])msg.obj;
String toastText = getString(R.string.account_size_changed, mAccount.getDescription(),
SizeFormatter.formatSize(getApplication(), sizes[0]), SizeFormatter.formatSize(getApplication(), sizes[1]));;
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG);
toast.show();
break;
}
case MSG_WORKING_ACCOUNT:
{
int res = msg.arg1;
String toastText = getString(res, mAccount.getDescription());
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT);
toast.show();
break;
}
case MSG_SYNC_MESSAGES: case MSG_SYNC_MESSAGES:
{ {
FolderInfoHolder folder = (FolderInfoHolder) ((Object[]) msg.obj)[0]; FolderInfoHolder folder = (FolderInfoHolder) ((Object[]) msg.obj)[0];
@ -312,6 +339,15 @@ public class FolderMessageList extends ExpandableListActivity
{ folder, messages }; { folder, messages };
sendMessage(msg); sendMessage(msg);
} }
public void workingAccount(int res)
{
android.os.Message msg = new android.os.Message();
msg.what = MSG_WORKING_ACCOUNT;
msg.arg1 = res;
sendMessage(msg);
}
public void removeMessage(FolderInfoHolder folder, MessageInfoHolder message) public void removeMessage(FolderInfoHolder folder, MessageInfoHolder message)
{ {
@ -321,6 +357,14 @@ public class FolderMessageList extends ExpandableListActivity
{ folder, message }; { folder, message };
sendMessage(msg); sendMessage(msg);
} }
public void accountSizeChanged(long oldSize, long newSize)
{
android.os.Message msg = new android.os.Message();
msg.what = MSG_ACCOUNT_SIZE_CHANGED;
msg.obj = new Long[] { oldSize, newSize };
sendMessage(msg);
}
public void folderLoading(String folder, boolean loading) public void folderLoading(String folder, boolean loading)
{ {
@ -587,6 +631,8 @@ public class FolderMessageList extends ExpandableListActivity
MessagingController.getInstance(getApplication()).addListener( MessagingController.getInstance(getApplication()).addListener(
mAdapter.mListener); mAdapter.mListener);
mAccount.refresh(Preferences.getPreferences(this)); mAccount.refresh(Preferences.getPreferences(this));
markAllRefresh();
onRefresh(false); onRefresh(false);
NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
@ -628,7 +674,8 @@ public class FolderMessageList extends ExpandableListActivity
*/ */
int position = mListView.getFlatListPosition(ExpandableListView int position = mListView.getFlatListPosition(ExpandableListView
.getPackedPositionForGroup(groupPosition)); .getPackedPositionForGroup(groupPosition));
mListView.setSelectionFromTop(position, 0);
mListView.setSelectionFromTop(position, 0);
} }
final FolderInfoHolder folder = (FolderInfoHolder) mAdapter final FolderInfoHolder folder = (FolderInfoHolder) mAdapter
@ -814,6 +861,11 @@ public class FolderMessageList extends ExpandableListActivity
} }
private void markAllRefresh()
{
mAdapter.mListener.accountReset(mAccount);
}
private void onCycleSort() private void onCycleSort()
{ {
SORT_TYPE[] sorts = SORT_TYPE.values(); SORT_TYPE[] sorts = SORT_TYPE.values();
@ -975,11 +1027,28 @@ public class FolderMessageList extends ExpandableListActivity
case R.id.empty_trash: case R.id.empty_trash:
onEmptyTrash(mAccount); onEmptyTrash(mAccount);
return true; return true;
case R.id.compact:
onCompact(mAccount);
return true;
case R.id.clear:
onClear(mAccount);
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
private void onCompact(Account account)
{
mHandler.workingAccount(R.string.compacting_account);
MessagingController.getInstance(getApplication()).compact(account, null);
}
private void onClear(Account account)
{
mHandler.workingAccount(R.string.clearing_account);
MessagingController.getInstance(getApplication()).clear(account, null);
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) public boolean onCreateOptionsMenu(Menu menu)
@ -1186,6 +1255,18 @@ public class FolderMessageList extends ExpandableListActivity
} }
mHandler.dataChanged(); mHandler.dataChanged();
} }
@Override
public void accountReset(Account account)
{
if (!account.equals(mAccount))
{
return;
}
for (FolderInfoHolder folder : mFolders)
{
folder.needsRefresh = true;
}
}
@Override @Override
public void listFolders(Account account, Folder[] folders) public void listFolders(Account account, Folder[] folders)
@ -1484,6 +1565,16 @@ public class FolderMessageList extends ExpandableListActivity
} }
mHandler.sendingOutbox(false); mHandler.sendingOutbox(false);
} }
public void accountSizeChanged(Account account, long oldSize, long newSize)
{
if (!account.equals(mAccount))
{
return;
}
mHandler.accountSizeChanged(oldSize, newSize);
}
@Override @Override
public void messageUidChanged(Account account, String folder, public void messageUidChanged(Account account, String folder,
@ -1807,19 +1898,20 @@ public class FolderMessageList extends ExpandableListActivity
{ {
holder.main.setText(getString(R.string.status_loading_more)); holder.main.setText(getString(R.string.status_loading_more));
holder.progress.setVisibility(View.VISIBLE); holder.progress.setVisibility(View.VISIBLE);
} else }
else
{ {
if (folder.lastCheckFailed == false) if (folder.lastCheckFailed == false)
{ {
holder.main.setText(String.format(getString(R.string.load_more_messages_fmt).toString(), holder.main.setText(String.format(getString(R.string.load_more_messages_fmt).toString(),
mAccount.getDisplayCount())); mAccount.getDisplayCount()));
} else }
else
{ {
holder.main.setText(getString(R.string.status_loading_more_failed)); holder.main.setText(getString(R.string.status_loading_more_failed));
} }
holder.progress.setVisibility(View.GONE); holder.progress.setVisibility(View.GONE);
} } return view;
return view;
} else } else
{ {
MessageInfoHolder message = (MessageInfoHolder) getChild(groupPosition, MessageInfoHolder message = (MessageInfoHolder) getChild(groupPosition,

View File

@ -140,7 +140,88 @@ public class LocalStore extends Store implements Serializable {
throw new Error("Database upgrade failed!"); throw new Error("Database upgrade failed!");
} }
} }
public long getSize()
{
long attachmentLength = 0;
File[] files = mAttachmentsDir.listFiles();
for (File file : files) {
if (file.exists()) {
attachmentLength += file.length();
}
}
File dbFile = new File(mPath);
return dbFile.length() + attachmentLength;
}
public void compact() throws MessagingException
{
Log.i(Email.LOG_TAG, "Before prune size = " + getSize());
pruneCachedAttachments();
Log.i(Email.LOG_TAG, "After prune / before compaction size = " + getSize());
mDb.execSQL("VACUUM");
Log.i(Email.LOG_TAG, "After compaction size = " + getSize());
}
public void clear() throws MessagingException
{
Log.i(Email.LOG_TAG, "Before prune size = " + getSize());
pruneCachedAttachments(true);
Log.i(Email.LOG_TAG, "After prune / before compaction size = " + getSize());
Log.i(Email.LOG_TAG, "Before clear folder count = " + getFolderCount());
Log.i(Email.LOG_TAG, "Before clear message count = " + getMessageCount());
Log.i(Email.LOG_TAG, "After prune / before clear size = " + getSize());
// 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
// been deleted locally. They take up no space, and are indicated with a null date.
mDb.execSQL("DELETE FROM messages WHERE date is not null and uid not like 'Local%'" );
compact();
Log.i(Email.LOG_TAG, "After clear message count = " + getMessageCount());
Log.i(Email.LOG_TAG, "After clear size = " + getSize());
}
public int getMessageCount() throws MessagingException {
Cursor cursor = null;
try {
cursor = mDb.rawQuery("SELECT COUNT(*) FROM messages", null);
cursor.moveToFirst();
int messageCount = cursor.getInt(0);
return messageCount;
}
finally {
if (cursor != null) {
cursor.close();
}
}
}
public int getFolderCount() throws MessagingException {
Cursor cursor = null;
try {
cursor = mDb.rawQuery("SELECT COUNT(*) FROM folders", null);
cursor.moveToFirst();
int messageCount = cursor.getInt(0);
return messageCount;
}
finally {
if (cursor != null) {
cursor.close();
}
}
}
@Override @Override
public LocalFolder getFolder(String name) throws MessagingException { public LocalFolder getFolder(String name) throws MessagingException {
return new LocalFolder(name); return new LocalFolder(name);
@ -204,49 +285,68 @@ public class LocalStore extends Store implements Serializable {
} }
} }
public void pruneCachedAttachments() throws MessagingException {
pruneCachedAttachments(false);
}
/** /**
* Deletes all cached attachments for the entire store. * Deletes all cached attachments for the entire store.
*/ */
public void pruneCachedAttachments() throws MessagingException { public void pruneCachedAttachments(boolean force) throws MessagingException {
if (force)
{
ContentValues cv = new ContentValues();
cv.putNull("content_uri");
mDb.update("attachments", cv, null, null);
}
File[] files = mAttachmentsDir.listFiles(); File[] files = mAttachmentsDir.listFiles();
for (File file : files) { for (File file : files) {
if (file.exists()) { if (file.exists()) {
if (!force) {
Cursor cursor = null;
try { try {
Cursor cursor = null; cursor = mDb.query(
try { "attachments",
cursor = mDb.query( new String[] { "store_data" },
"attachments", "id = ?",
new String[] { "store_data" }, new String[] { file.getName() },
"id = ?", null,
new String[] { file.getName() }, null,
null, null);
null, if (cursor.moveToNext()) {
null); if (cursor.getString(0) == null) {
if (cursor.moveToNext()) { Log.d(Email.LOG_TAG, "Attachment " + file.getAbsolutePath() + " has no store data, not deleting");
if (cursor.getString(0) == null) { /*
/* * If the attachment has no store data it is not recoverable, so
* If the attachment has no store data it is not recoverable, so * we won't delete it.
* we won't delete it. */
*/ continue;
continue; }
} }
} }
finally {
if (cursor != null) {
cursor.close();
} }
finally { }
if (cursor != null) { }
cursor.close(); if (!force)
} {
} try
ContentValues cv = new ContentValues(); {
cv.putNull("content_uri"); ContentValues cv = new ContentValues();
mDb.update("attachments", cv, "id = ?", new String[] { file.getName() }); cv.putNull("content_uri");
mDb.update("attachments", cv, "id = ?", new String[] { file.getName() });
} }
catch (Exception e) { catch (Exception e) {
/* /*
* If the row has gone away before we got to mark it not-downloaded that's * If the row has gone away before we got to mark it not-downloaded that's
* okay. * okay.
*/ */
} }
}
Log.d(Email.LOG_TAG, "Deleting attachment " + file.getAbsolutePath() + ", which is of size " + file.length());
if (!file.delete()) { if (!file.delete()) {
file.deleteOnExit(); file.deleteOnExit();
} }
@ -1292,7 +1392,9 @@ public class LocalStore extends Store implements Serializable {
/* /*
* Delete all of the messages' content to save space. * Delete all of the messages' content to save space.
*/ */
mDb.execSQL( ((LocalFolder) mFolder).deleteAttachments(getUid());
mDb.execSQL(
"UPDATE messages SET " + "UPDATE messages SET " +
"subject = NULL, " + "subject = NULL, " +
"sender_list = NULL, " + "sender_list = NULL, " +
@ -1307,12 +1409,11 @@ public class LocalStore extends Store implements Serializable {
new Object[] { new Object[] {
mId mId
}); });
((LocalFolder) mFolder).deleteAttachments(getUid());
/* /*
* Delete all of the messages' attachments to save space. * Delete all of the messages' attachments to save space.
*/ */
// shouldn't the trigger take care of this? -- danapple
mDb.execSQL("DELETE FROM attachments WHERE id = ?", mDb.execSQL("DELETE FROM attachments WHERE id = ?",
new Object[] { new Object[] {
mId mId