See ChangeLog

Monotone-Parent: 73de56ded7f5b5ba79857c706ecef4d70ac3113b
Monotone-Revision: 5c6353cb6270d51a457d46a8fe98dadae4f37193

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2011-07-07T13:58:50
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Francis Lachapelle 2011-07-07 13:58:50 +00:00
parent 485e53258f
commit 34f2904f0d
9 changed files with 106 additions and 42 deletions

View File

@ -1,3 +1,21 @@
2011-07-08 Francis Lachapelle <flachapelle@inverse.ca>
* UI/MailerUI/UIxMailListActions.m (-getUIDsAndHeadersInFolder)
(-getSortedUIDsAction): both methods now return the inbox quota
along with the other data.
* UI/MailerUI/UIxMailFolderActions.m (-batchDeleteAction): return
the inbox quota when the messages were successfully deleted.
* SoObjects/Mailer/SOGoMailFolder.m
(-deleteUIDs:useTrashFolder:inContext:): when not using the trash folder
or when deleting a message within the trash folder, we now expunge the
folder immediately.
* UI/WebServerResources/SOGoMailDataSource.js (init): quotas
information can now be passed as argument. It will be passed back
to the method "updateQuotas" from MailerUI.js.
2011-07-08 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2011-07-08 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* OpenChange/MAPIStoreContext.m (-openDir:): implemented. * OpenChange/MAPIStoreContext.m (-openDir:): implemented.

View File

@ -296,8 +296,12 @@ static NSString *defaultUserID = @"anyone";
// If we are deleting messages within the Trash folder itself, we // If we are deleting messages within the Trash folder itself, we
// do not, of course, try to move messages to the Trash folder. // do not, of course, try to move messages to the Trash folder.
if (![folderName isEqualToString: [self relativeImap4Name]]) if ([folderName isEqualToString: [self relativeImap4Name]])
{ {
withTrash = NO;
}
else
{
// If our Trash folder doesn't exist when we try to copy messages // If our Trash folder doesn't exist when we try to copy messages
// to it, we create it. // to it, we create it.
result = [[client status: folderName flags: [NSArray arrayWithObject: @"UIDVALIDITY"]] result = [[client status: folderName flags: [NSArray arrayWithObject: @"UIDVALIDITY"]]
@ -331,11 +335,20 @@ static NSString *defaultUserID = @"anyone";
objectForKey: @"result"]; objectForKey: @"result"];
if ([result boolValue]) if ([result boolValue])
{ {
[self markForExpunge]; if (withTrash)
if (trashFolder) {
[trashFolder flushMailCaches]; [self markForExpunge];
error = nil; if (trashFolder)
} [trashFolder flushMailCaches];
error = nil;
}
else
{
// When not using a trash folder, expunge the current folder
// immediately
error = [self expunge];
}
}
else else
error error
= [NSException exceptionWithHTTPStatus:500 = [NSException exceptionWithHTTPStatus:500

View File

@ -181,13 +181,11 @@
NSArray *folders; NSArray *folders;
NSDictionary *data; NSDictionary *data;
WOResponse *response; WOResponse *response;
id inboxQuota;
co = [self clientObject]; co = [self clientObject];
rawFolders = [[co allFolderPaths] objectEnumerator]; rawFolders = [[co allFolderPaths] objectEnumerator];
folders = [self _jsonFolders: rawFolders]; folders = [self _jsonFolders: rawFolders];
inboxQuota = nil;
// The parameters order is important here, as if the server doesn't support // The parameters order is important here, as if the server doesn't support
// quota, inboxQuota will be nil and it'll terminate the list of objects/keys. // quota, inboxQuota will be nil and it'll terminate the list of objects/keys.

View File

@ -229,9 +229,11 @@
- (WOResponse *) batchDeleteAction - (WOResponse *) batchDeleteAction
{ {
SOGoMailFolder *co; SOGoMailFolder *co;
SOGoMailAccount *account;
WOResponse *response; WOResponse *response;
NSArray *uids; NSArray *uids;
NSString *value; NSString *value;
NSDictionary *data;
BOOL withoutTrash; BOOL withoutTrash;
co = [self clientObject]; co = [self clientObject];
@ -244,7 +246,12 @@
uids = [value componentsSeparatedByString: @","]; uids = [value componentsSeparatedByString: @","];
response = (WOResponse *) [co deleteUIDs: uids useTrashFolder: !withoutTrash inContext: context]; response = (WOResponse *) [co deleteUIDs: uids useTrashFolder: !withoutTrash inContext: context];
if (!response) if (!response)
response = [self responseWith204]; {
account = [co mailAccountFolder];
data = [NSDictionary dictionaryWithObjectsAndKeys: [account getInboxQuota], @"quotas", nil];
response = [self responseWithStatus: 200
andString: [data jsonRepresentation]];
}
} }
else else
{ {
@ -520,6 +527,8 @@
if (!error) if (!error)
{ {
[co flushMailCaches]; [co flushMailCaches];
// Delete folders within the trash
connection = [co imap4Connection]; connection = [co imap4Connection];
subfolders = [[co allFolderURLs] objectEnumerator]; subfolders = [[co allFolderURLs] objectEnumerator];
while ((currentURL = [subfolders nextObject])) while ((currentURL = [subfolders nextObject]))
@ -585,6 +594,7 @@
EOQualifier *searchQualifier; EOQualifier *searchQualifier;
NSArray *searchResult; NSArray *searchResult;
NSDictionary *imapResult; NSDictionary *imapResult;
// NSMutableDictionary *data;
NGImap4Connection *connection; NGImap4Connection *connection;
NGImap4Client *client; NGImap4Client *client;
int unseen; int unseen;
@ -606,7 +616,7 @@
} }
else else
unseen = 0; unseen = 0;
return [NSDictionary return [NSDictionary
dictionaryWithObject: [NSNumber numberWithInt: unseen] dictionaryWithObject: [NSNumber numberWithInt: unseen]
forKey: @"unseen"]; forKey: @"unseen"];

View File

@ -48,6 +48,7 @@
#import <Mailer/NSString+Mail.h> #import <Mailer/NSString+Mail.h>
#import <Mailer/SOGoDraftsFolder.h> #import <Mailer/SOGoDraftsFolder.h>
#import <Mailer/SOGoMailAccount.h>
#import <Mailer/SOGoMailFolder.h> #import <Mailer/SOGoMailFolder.h>
#import <Mailer/SOGoMailObject.h> #import <Mailer/SOGoMailObject.h>
#import <Mailer/SOGoSentFolder.h> #import <Mailer/SOGoSentFolder.h>
@ -347,14 +348,12 @@
BOOL asc; BOOL asc;
SOGoUser *activeUser; SOGoUser *activeUser;
SOGoUserSettings *us; SOGoUserSettings *us;
SOGoMailAccounts *clientObject;
sort = [self imap4SortKey]; sort = [self imap4SortKey];
ascending = [[context request] formValueForKey: @"asc"]; ascending = [[context request] formValueForKey: @"asc"];
asc = [ascending boolValue]; asc = [ascending boolValue];
activeUser = [context activeUser]; activeUser = [context activeUser];
clientObject = [self clientObject];
module = @"Mail"; module = @"Mail";
us = [activeUser userSettings]; us = [activeUser userSettings];
moduleSettings = [us objectForKey: module]; moduleSettings = [us objectForKey: module];
@ -630,6 +629,8 @@
NSDictionary *data; NSDictionary *data;
NSRange r; NSRange r;
int count; int count;
SOGoMailAccount *account;
id quota;
uids = [self getSortedUIDsInFolder: mailFolder]; // retrieves the form parameters "sort" and "asc" uids = [self getSortedUIDsInFolder: mailFolder]; // retrieves the form parameters "sort" and "asc"
@ -649,9 +650,14 @@
sortByThread = NO; sortByThread = NO;
} }
// We also return the inbox quota
account = [mailFolder mailAccountFolder];
quota = [account getInboxQuota];
data = [NSDictionary dictionaryWithObjectsAndKeys: uids, @"uids", data = [NSDictionary dictionaryWithObjectsAndKeys: uids, @"uids",
headers, @"headers", headers, @"headers",
[NSNumber numberWithBool: sortByThread], @"threaded", nil]; [NSNumber numberWithBool: sortByThread], @"threaded",
quota, @"quotas", nil];
return data; return data;
} }
@ -663,7 +669,9 @@
NSDictionary *data; NSDictionary *data;
NSArray *uids, *threadedUids; NSArray *uids, *threadedUids;
NSString *noHeaders; NSString *noHeaders;
SOGoMailAccount *account;
SOGoMailFolder *folder; SOGoMailFolder *folder;
id quota;
WORequest *request; WORequest *request;
WOResponse *response; WOResponse *response;
@ -672,9 +680,11 @@
[response setHeader: @"text/plain; charset=utf-8" [response setHeader: @"text/plain; charset=utf-8"
forKey: @"content-type"]; forKey: @"content-type"];
folder = [self clientObject]; folder = [self clientObject];
// TODO: we might want to flush the caches? // TODO: we might want to flush the caches?
//[folder flushMailCaches]; //[folder flushMailCaches];
[folder expungeLastMarkedFolder]; [folder expungeLastMarkedFolder];
noHeaders = [request formValueForKey: @"no_headers"]; noHeaders = [request formValueForKey: @"no_headers"];
if ([noHeaders length]) if ([noHeaders length])
{ {
@ -687,8 +697,14 @@
else else
sortByThread = NO; sortByThread = NO;
} }
// We also return the inbox quota
account = [folder mailAccountFolder];
quota = [account getInboxQuota];
data = [NSDictionary dictionaryWithObjectsAndKeys: uids, @"uids", data = [NSDictionary dictionaryWithObjectsAndKeys: uids, @"uids",
[NSNumber numberWithBool: sortByThread], @"threaded", nil]; [NSNumber numberWithBool: sortByThread], @"threaded",
quota, @"quotas", nil];
} }
else else
data = [self getUIDsAndHeadersInFolder: folder]; data = [self getUIDsAndHeadersInFolder: folder];

View File

@ -162,14 +162,12 @@
SOGoMailAccount *account; SOGoMailAccount *account;
SOGoMailFolder *inbox; SOGoMailFolder *inbox;
NSDictionary *data; NSDictionary *data;
SOGoUser *activeUser;
UIxMailListActions *actions; UIxMailListActions *actions;
[self _setupContext]; [self _setupContext];
#warning this code is dirty: we should not invoke UIxMailListActions from here! #warning this code is dirty: we should not invoke UIxMailListActions from here!
actions = [[[UIxMailListActions new] initWithRequest: [context request]] autorelease]; actions = [[[UIxMailListActions new] initWithRequest: [context request]] autorelease];
activeUser = [context activeUser];
accounts = [self clientObject]; accounts = [self clientObject];
account = [accounts lookupName: @"0" inContext: context acquire: NO]; account = [accounts lookupName: @"0" inContext: context acquire: NO];
@ -472,13 +470,6 @@
tmpKeys = [NSArray arrayWithObjects: @"headerClass", @"headerId", @"value", tmpKeys = [NSArray arrayWithObjects: @"headerClass", @"headerId", @"value",
nil]; nil];
tmpColumns
= [NSArray arrayWithObjects: @"messageSubjectColumn tbtv_headercell sortableTableHeader resizable",
@"subjectHeader", @"Subject", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Subject"];
tmpColumns tmpColumns
= [NSArray arrayWithObjects: @"messageThreadColumn tbtv_headercell", = [NSArray arrayWithObjects: @"messageThreadColumn tbtv_headercell",
@"invisibleHeader", @"Thread", nil]; @"invisibleHeader", @"Thread", nil];
@ -486,6 +477,13 @@
forKeys: tmpKeys] forKeys: tmpKeys]
forKey: @"Thread"]; forKey: @"Thread"];
tmpColumns
= [NSArray arrayWithObjects: @"messageSubjectColumn tbtv_headercell sortableTableHeader resizable",
@"subjectHeader", @"Subject", nil];
[columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns
forKeys: tmpKeys]
forKey: @"Subject"];
tmpColumns tmpColumns
= [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell", = [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell",
@"invisibleHeader", @"Flagged", nil]; @"invisibleHeader", @"Flagged", nil];
@ -582,7 +580,6 @@
if (i == NSNotFound) if (i == NSNotFound)
[finalOrder insertObject: @"Thread" atIndex: 0]; [finalOrder insertObject: @"Thread" atIndex: 0];
} }
if ([self showToAddress]) if ([self showToAddress])
{ {
i = [finalOrder indexOfObject: @"From"]; i = [finalOrder indexOfObject: @"From"];

View File

@ -315,8 +315,8 @@ function mailListToggleMessagesFlagged(row) {
} }
} }
if (selectedRowsId.length > 0) { if (selectedRowsId.length > 0) {
var firstTd = row.childElements().first(); var td = row.down("td.messageFlagColumn");
var img = firstTd.childElements().first(); var img = td.childElements().first();
var action = "markMessageFlagged"; var action = "markMessageFlagged";
var flagged = true; var flagged = true;
@ -476,15 +476,16 @@ function deleteSelectedMessages(sender) {
var nextUid = nextRow.id.substr(4); var nextUid = nextRow.id.substr(4);
var nextIndex = Mailer.dataTable.dataSource.indexOf(nextUid); var nextIndex = Mailer.dataTable.dataSource.indexOf(nextUid);
Mailer.dataTable.dataSource.uids[nextIndex][2] = 1; // mark it as "first" Mailer.dataTable.dataSource.uids[nextIndex][2] = 1; // mark it as "first"
Mailer.dataTable.invalidate(nextUid, true); Mailer.dataTable.dataSource.invalidate(nextUid); // next refresh will reload headers for row
} }
if (nextRow.id.startsWith('row_')) { if (nextRow.id.startsWith('row_')) {
Mailer.currentMessages[Mailer.currentMailbox] = nextRow.id.substr(4); Mailer.currentMessages[Mailer.currentMailbox] = nextRow.id.substr(4);
nextRow.selectElement(); nextRow.selectElement();
if (loadMessage(Mailer.currentMessages[Mailer.currentMailbox]) && !refreshFolder) if (loadMessage(Mailer.currentMessages[Mailer.currentMailbox]) && !refreshFolder) {
// Seen state has changed // Seen state has changed
Mailer.dataTable.invalidate(Mailer.currentMessages[Mailer.currentMailbox], true); Mailer.dataTable.dataSource.invalidate(Mailer.currentMessages[Mailer.currentMailbox]);
refreshFolder = true; refreshFolder = true;
}
} }
} }
else { else {
@ -496,7 +497,8 @@ function deleteSelectedMessages(sender) {
lastClickedRow = nextRow.rowIndex; lastClickedRow = nextRow.rowIndex;
lastClickedRowId = nextRow.id; lastClickedRowId = nextRow.id;
} }
deleteCachedMailboxByType("trash"); if (Mailer.currentMailboxType != "trash")
deleteCachedMailboxByType("trash");
} }
else { else {
Mailer.dataTable.remove(uid); Mailer.dataTable.remove(uid);
@ -522,8 +524,11 @@ function deleteSelectedMessages(sender) {
} }
function deleteSelectedMessagesCallback(http) { function deleteSelectedMessagesCallback(http) {
if (isHttpStatus204(http.status)) { if (http.status == 200) {
var data = http.callbackData; var data = http.callbackData;
var rdata = http.responseText.evalJSON(true);
if (rdata.quotas && data["mailbox"].startsWith('/0/'))
updateQuotas(rdata.quotas);
if (data["refreshUnseenCount"]) if (data["refreshUnseenCount"])
// TODO : the unseen count should be returned when calling the batchDelete remote action, // TODO : the unseen count should be returned when calling the batchDelete remote action,
// in order to avoid this extra AJAX call. // in order to avoid this extra AJAX call.
@ -539,7 +544,8 @@ function deleteSelectedMessagesCallback(http) {
} }
else { else {
var html = new Element('div').update(http.responseText); var html = new Element('div').update(http.responseText);
log ("Messages deletion failed (" + http.status + ") : " + html.down('p').innerHTML); log ("Messages deletion failed (" + http.status + ") : ");
log (html.down('p').innerHTML);
showAlertDialog(_("Operation failed")); showAlertDialog(_("Operation failed"));
refreshCurrentFolder(); refreshCurrentFolder();
} }
@ -1637,7 +1643,7 @@ function loadMessageCallback(http) {
// Warning: If the user can't set the read/unread flag, it won't // Warning: If the user can't set the read/unread flag, it won't
// be reflected in the view unless we force the refresh. // be reflected in the view unless we force the refresh.
if (http.callbackData.seenStateHasChanged) if (http.callbackData.seenStateHasChanged)
Mailer.dataTable.invalidate(msguid, true); Mailer.dataTable.dataSource.invalidate(msguid);
} }
var cachedMessage = new Array(); var cachedMessage = new Array();
cachedMessage['idx'] = Mailer.currentMailbox + '/' + msguid; cachedMessage['idx'] = Mailer.currentMailbox + '/' + msguid;
@ -2068,7 +2074,7 @@ function updateQuotas(quotas) {
if (quotas) if (quotas)
Mailer.quotas = quotas; Mailer.quotas = quotas;
if (Mailer.quotas && parseInt(Mailer.quotas.maxQuota) > 0) { if (Mailer.quotas && parseInt(Mailer.quotas.maxQuota) > 0) {
log ("updating quotas"); log ("updating quotas " + Mailer.quotas.usedSpace + "/" + Mailer.quotas.maxQuota);
var treeContent = $("folderTreeContent"); var treeContent = $("folderTreeContent");
var tree = $("mailboxTree"); var tree = $("mailboxTree");
var quotaDiv = $("quotaIndicator"); var quotaDiv = $("quotaIndicator");

View File

@ -292,6 +292,7 @@ var SOGoDataTableInterface = {
invalidate: function(uid, withoutRefresh) { invalidate: function(uid, withoutRefresh) {
// Refetch the data for uid. Only refresh the data table if // Refetch the data for uid. Only refresh the data table if
// necessary. // necessary.
// log ("DataTable.invalidate(" + uid + ", with" + (withoutRefresh?"out":"") + " refresh)");
var index = this.dataSource.invalidate(uid); var index = this.dataSource.invalidate(uid);
this.currentRenderID = index + "-" + 1; this.currentRenderID = index + "-" + 1;
this.dataSource.getData(this.currentRenderID, this.dataSource.getData(this.currentRenderID,

View File

@ -35,6 +35,7 @@ SOGoMailDataSource = Class.create({
}, },
remove: function(uid) { remove: function(uid) {
// log ("MailDataSource.remove(" + uid + ")");
var index = this.invalidate(uid); var index = this.invalidate(uid);
if (index >= 0) { if (index >= 0) {
this.uids.splice(index, 1); this.uids.splice(index, 1);
@ -43,7 +44,7 @@ SOGoMailDataSource = Class.create({
return index; return index;
}, },
init: function(uids, threaded, headers) { init: function(uids, threaded, headers, quotas) {
this.uids = uids; this.uids = uids;
if (typeof threaded != "undefined") { if (typeof threaded != "undefined") {
this.threaded = threaded; this.threaded = threaded;
@ -52,6 +53,9 @@ SOGoMailDataSource = Class.create({
} }
// log ("MailDataSource.init() " + this.uids.length + " uids loaded"); // log ("MailDataSource.init() " + this.uids.length + " uids loaded");
if (quotas && Object.isFunction(updateQuotas))
updateQuotas(quotas);
if (headers) { if (headers) {
var keys = headers[0]; var keys = headers[0];
for (var i = 1; i < headers.length; i++) { for (var i = 1; i < headers.length; i++) {
@ -89,7 +93,7 @@ SOGoMailDataSource = Class.create({
if (http.responseText.length > 0) { if (http.responseText.length > 0) {
var data = http.responseText.evalJSON(true); var data = http.responseText.evalJSON(true);
if (data.uids) if (data.uids)
this.init(data.uids, data.threaded, data.headers); this.init(data.uids, data.threaded, data.headers, data.quotas);
else else
this.init(data); this.init(data);
if (this.delayedGetData) { if (this.delayedGetData) {
@ -146,7 +150,8 @@ SOGoMailDataSource = Class.create({
for (i = 0, j = start; j < end; j++) { for (i = 0, j = start; j < end; j++) {
var uid = this.threaded? this.uids[j][0] : this.uids[j]; var uid = this.threaded? this.uids[j][0] : this.uids[j];
if (!this.cache.get(uid)) { if (!this.cache.get(uid)) {
missingUids[i] = uid; // log ("MailDataSource._getData missing headers of uid " + uid + " at index " + j + (this.threaded? " (":" (non-") + "threaded)");
missingUids[i] = uid;
i++; i++;
} }
} }
@ -168,9 +173,9 @@ SOGoMailDataSource = Class.create({
if (this.ajaxGetData) { if (this.ajaxGetData) {
this.ajaxGetData.aborted = true; this.ajaxGetData.aborted = true;
this.ajaxGetData.abort(); this.ajaxGetData.abort();
// log ("MailDataSource._getData() aborted previous AJAX request"); // log ("MailDataSource._getRemoteData() aborted previous AJAX request");
} }
// log ("MailDataSource._getData() fetching headers of " + urlParams); // log ("MailDataSource._getRemoteData() fetching headers of " + urlParams);
this.ajaxGetData = triggerAjaxRequest(this.url + "/headers", this.ajaxGetData = triggerAjaxRequest(this.url + "/headers",
this._getRemoteDataCallback.bind(this), this._getRemoteDataCallback.bind(this),
callbackData, callbackData,
@ -191,7 +196,7 @@ SOGoMailDataSource = Class.create({
header[keys[j]] = headers[i][j]; header[keys[j]] = headers[i][j];
this.cache.set(header["uid"], header); this.cache.set(header["uid"], header);
} }
if (data["callbackFunction"]) if (data["callbackFunction"])
this._returnData(data["callbackFunction"], data["id"], data["start"], data["end"]); this._returnData(data["callbackFunction"], data["id"], data["start"], data["end"]);
} }
@ -211,7 +216,7 @@ SOGoMailDataSource = Class.create({
// Add thread-related data // Add thread-related data
if (parseInt(this.uids[i][2]) > 0) if (parseInt(this.uids[i][2]) > 0)
data[j]['Thread'] = '&nbsp;'; //'<img class="messageThread" src="' + ResourcesURL + '/arrow-down.png">'; data[j]['Thread'] = '&nbsp;'; //'<img class="messageThread" src="' + ResourcesURL + '/arrow-down.png">';
else else if (data[j]['Thread'])
delete data[j]['Thread']; delete data[j]['Thread'];
if (parseInt(this.uids[i][1]) > -1) if (parseInt(this.uids[i][1]) > -1)
data[j]['ThreadLevel'] = this.uids[i][1]; data[j]['ThreadLevel'] = this.uids[i][1];