add support for unlimited number of tags per profile and per message, replace labels with tags, sr=mscott, sspitzer 114656

This commit is contained in:
bienvenu%nventure.com 2006-06-09 14:26:31 +00:00
parent 91f6c5a433
commit d06f8bbbcc
74 changed files with 1921 additions and 552 deletions

View File

@ -214,12 +214,6 @@ var DefaultController =
case "cmd_applyFilters":
case "cmd_runJunkControls":
case "cmd_deleteJunk":
case "cmd_label0":
case "cmd_label1":
case "cmd_label2":
case "cmd_label3":
case "cmd_label4":
case "cmd_label5":
case "button_file":
case "cmd_file":
case "cmd_emptyTrash":
@ -346,12 +340,6 @@ var DefaultController =
case "button_mark":
case "cmd_markAsRead":
case "cmd_markThreadAsRead":
case "cmd_label0":
case "cmd_label1":
case "cmd_label2":
case "cmd_label3":
case "cmd_label4":
case "cmd_label5":
return GetNumSelectedMessages() > 0;
case "button_previous":
case "button_next":
@ -646,24 +634,6 @@ var DefaultController =
return;
case "cmd_deleteJunk":
deleteJunkInFolder();
return;
case "cmd_label0":
gDBView.doCommand(nsMsgViewCommandType.label0);
return;
case "cmd_label1":
gDBView.doCommand(nsMsgViewCommandType.label1);
return;
case "cmd_label2":
gDBView.doCommand(nsMsgViewCommandType.label2);
return;
case "cmd_label3":
gDBView.doCommand(nsMsgViewCommandType.label3);
return;
case "cmd_label4":
gDBView.doCommand(nsMsgViewCommandType.label4);
return;
case "cmd_label5":
gDBView.doCommand(nsMsgViewCommandType.label5);
return;
case "cmd_emptyTrash":
MsgEmptyTrash();

View File

@ -26,7 +26,7 @@
# Håkan Waara <hwaara@chello.se>
# Jan Varga <varga@nixcorp.com>
# Seth Spitzer <sspitzer@netscape.com>
# David Bienvenu <bienvenu@netscape.com>
# David Bienvenu <bienvenu@nventure.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@ -554,58 +554,92 @@ function SetMenuItemLabel(menuItemId, customLabel)
menuItem.setAttribute('label', customLabel);
}
function InitMessageLabel(menuType)
function TagCurMessage(key)
{
var color;
// ###need to do all selected messsages
var msgHdr = gDBView.hdrForFirstSelectedMessage;
var messages = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
messages.AppendElement(msgHdr);
msgHdr.folder.addKeywordToMessages(messages, key);
}
try
function UnTagCurMessage(key)
{
// ###need to do all selected messsages
var msgHdr = gDBView.hdrForFirstSelectedMessage;
var messages = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
messages.AppendElement(msgHdr);
msgHdr.folder.removeKeywordFromMessages(messages, key);
}
function AddTag()
{
var args = {result: "", okCallback: AddTagCallback};
var dialog = window.openDialog(
"chrome://messenger/content/newTagDialog.xul",
"",
"chrome,titlebar,modal",
args);
}
function AddTagCallback(name, color)
{
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"].getService(Components.interfaces.nsIMsgTagService);
tagService.addTag(name, color);
TagCurMessage(name);
return true;
}
function InitMessageTags(menuType)
{
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"].getService(Components.interfaces.nsIMsgTagService);
var allTags = tagService.tagEnumerator;
var allKeys = tagService.keyEnumerator;
// remove any existing entries...
var menuItemId = menuType + "-tagpopup";
var menupopupNode = document.getElementById(menuItemId);
for (var i = menupopupNode.childNodes.length - 1; i >= 0; --i)
menupopupNode.removeChild(menupopupNode.childNodes[i]);
// now rebuild the list
var msgHdr = gDBView.hdrForFirstSelectedMessage;
var curKeys = msgHdr.getStringProperty("keywords");
var newMenuItem;
var curMsgHdrKeyArray = curKeys.split(" ");
while (allTags.hasMore())
{
var tag = allTags.getNext();
var key = allKeys.getNext();
// TODO we want to either remove or "check" the tags that already exist
newMenuItem = document.createElement('menuitem');
newMenuItem.setAttribute('label', tag);
newMenuItem.setAttribute('value', key);
newMenuItem.setAttribute('type', 'checkbox');
var keySet = false;
for ( var index = 0; index < curMsgHdrKeyArray.length; index++ )
{
var isChecked = true;
var checkedLabel = gDBView.hdrForFirstSelectedMessage.label;
}
catch(ex)
{
isChecked = false;
if (key == curMsgHdrKeyArray[index])
{
keySet = true;
break;
}
}
// if we already have this tag, we should change the command to "UnTag"
var command = ((keySet) ? "Un" : "") + "TagCurMessage(" + "'" + key + "');";
newMenuItem.setAttribute('oncommand', command);
newMenuItem.setAttribute('checked', keySet);
menupopupNode.appendChild(newMenuItem);
}
var menuseparator = document.createElement('menuseparator');
menupopupNode.appendChild(menuseparator);
for (var label = 0; label <= 5; label++)
{
try
{
var prefString = gPrefBranch.getComplexValue("mailnews.labels.description." + label,
Components.interfaces.nsIPrefLocalizedString);
var formattedPrefString = gMessengerBundle.getFormattedString("labelMenuItemFormat" + label,
[prefString], 1);
var menuItemId = menuType + "-labelMenuItem" + label;
var menuItem = document.getElementById(menuItemId);
SetMenuItemLabel(menuItemId, formattedPrefString);
if (isChecked && label == checkedLabel)
menuItem.setAttribute("checked", "true");
else
menuItem.setAttribute("checked", "false");
// commented out for now until UE decides on how to show the Labels menu items.
// This code will color either the text or background for the Labels menu items.
/*****
if (label != 0)
{
color = prefBranch.getCharPref("mailnews.labels.color." + label);
// this colors the text of the menuitem only.
//menuItem.setAttribute("style", ("color: " + color));
// this colors the background of the menuitem and
// when selected, text becomes white.
//menuItem.setAttribute("style", ("color: #FFFFFF"));
//menuItem.setAttribute("style", ("background-color: " + color));
}
****/
}
catch(ex)
{
}
}
document.commandDispatcher.updateCommands('create-menu-label');
newMenuItem = document.createElement('menuitem');
newMenuItem.setAttribute('label', gMessengerBundle.getString("newTag"));
newMenuItem.setAttribute('oncommand', "AddTag()");
menupopupNode.appendChild(newMenuItem);
}
function InitMessageMark()

View File

@ -606,45 +606,8 @@
</rule>
</template>
</menu>
<menu id="threadPaneContext-labels" label="&labelMenu.label;" accesskey="&labelMenu.accesskey;">
<menupopup onpopupshowing="InitMessageLabel('threadPaneContext')">
<menuitem
id="threadPaneContext-labelMenuItem0"
type="radio"
checked="false"
accesskey="&labelCmd0.accesskey;"
observes="cmd_label0"/>
<menuseparator/>
<menuitem
id="threadPaneContext-labelMenuItem1"
type="radio"
checked="false"
accesskey="&labelCmd1.accesskey;"
observes="cmd_label1"/>
<menuitem
id="threadPaneContext-labelMenuItem2"
type="radio"
checked="false"
accesskey="&labelCmd2.accesskey;"
observes="cmd_label2"/>
<menuitem
id="threadPaneContext-labelMenuItem3"
type="radio"
checked="false"
accesskey="&labelCmd3.accesskey;"
observes="cmd_label3"/>
<menuitem
id="threadPaneContext-labelMenuItem4"
type="radio"
checked="false"
accesskey="&labelCmd4.accesskey;"
observes="cmd_label4"/>
<menuitem
id="threadPaneContext-labelMenuItem5"
type="radio"
checked="false"
accesskey="&labelCmd5.accesskey;"
observes="cmd_label5"/>
<menu id="threadPaneContext-tags" label="&tagMenu.label;" accesskey="&tagMenu.accesskey;">
<menupopup id="threadPaneContext-tagpopup" onpopupshowing="InitMessageTags('threadPaneContext')">
</menupopup>
</menu>
<menu id="threadPaneContext-mark" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
@ -948,46 +911,9 @@
</rule>
</template>
</menu>
<menuseparator id="messagePaneContext-sep-labels-1"/>
<menu id="messagePaneContext-labels" label="&labelMenu.label;" accesskey="&labelMenu.accesskey;">
<menupopup onpopupshowing="InitMessageLabel('messagePaneContext')">
<menuitem
id="messagePaneContext-labelMenuItem0"
type="radio"
checked="false"
accesskey="&labelCmd0.accesskey;"
observes="cmd_label0"/>
<menuseparator/>
<menuitem
id="messagePaneContext-labelMenuItem1"
type="radio"
checked="false"
accesskey="&labelCmd1.accesskey;"
observes="cmd_label1"/>
<menuitem
id="messagePaneContext-labelMenuItem2"
type="radio"
checked="false"
accesskey="&labelCmd2.accesskey;"
observes="cmd_label2"/>
<menuitem
id="messagePaneContext-labelMenuItem3"
type="radio"
checked="false"
accesskey="&labelCmd3.accesskey;"
observes="cmd_label3"/>
<menuitem
id="messagePaneContext-labelMenuItem4"
type="radio"
checked="false"
accesskey="&labelCmd4.accesskey;"
observes="cmd_label4"/>
<menuitem
id="messagePaneContext-labelMenuItem5"
type="radio"
checked="false"
accesskey="&labelCmd5.accesskey;"
observes="cmd_label5"/>
<menuseparator id="messagePaneContext-sep-tags-1"/>
<menu id="messagePaneContext-tags" label="&tagMenu.label;" accesskey="&tagMenu.accesskey;">
<menupopup id="messagePaneContext-tagpopup" onpopupshowing="InitMessageTags('messagePaneContext')">
</menupopup>
</menu>
<menu id="messagePaneContext-mark" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
@ -1613,85 +1539,48 @@
</template>
</menu>
<menu id="labelMenu" label="&labelMenu.label;" accesskey="&labelMenu.accesskey;">
<menupopup id="menuPopup-labels" onpopupshowing="InitMessageLabel('menuPopup')">
<menuitem
id="menuPopup-labelMenuItem0"
type="radio"
checked="false"
accesskey="&labelCmd0.accesskey;"
observes="cmd_label0"/>
<menuseparator/>
<menuitem
id="menuPopup-labelMenuItem1"
type="radio"
checked="false"
accesskey="&labelCmd1.accesskey;"
observes="cmd_label1"/>
<menuitem
id="menuPopup-labelMenuItem2"
type="radio"
checked="false"
accesskey="&labelCmd2.accesskey;"
observes="cmd_label2"/>
<menuitem
id="menuPopup-labelMenuItem3"
type="radio"
checked="false"
accesskey="&labelCmd3.accesskey;"
observes="cmd_label3"/>
<menuitem
id="menuPopup-labelMenuItem4"
type="radio"
checked="false"
accesskey="&labelCmd4.accesskey;"
observes="cmd_label4"/>
<menuitem
id="menuPopup-labelMenuItem5"
type="radio"
checked="false"
accesskey="&labelCmd5.accesskey;"
observes="cmd_label5"/>
</menupopup>
</menu>
<menu id="markMenu" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
<menupopup onpopupshowing="InitMessageMark()">
<menuitem type="checkbox" id="markReadMenuItem" label="&markAsReadCmd.label;" accesskey="&markAsReadCmd.accesskey;" observes="cmd_markAsRead"
#ifndef XP_MACOSX
<menu id="messagePaneContext-tags" label="&tagMenu.label;" accesskey="&tagMenu.accesskey;">
<menupopup id="messagePaneContext-tagpopup" onpopupshowing="InitMessageTags('messagePaneContext')">
</menupopup>
</menu>
<menu id="markMenu" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
<menupopup onpopupshowing="InitMessageMark()">
<menuitem type="checkbox" id="markReadMenuItem" label="&markAsReadCmd.label;" accesskey="&markAsReadCmd.accesskey;" observes="cmd_markAsRead"
ifndef="" XP_MACOSX=""
key="key_toggleRead"
#endif
endif=""
/>
<menuitem label="&markThreadAsReadCmd.label;" accesskey="&markThreadAsReadCmd.accesskey;" observes="cmd_markThreadAsRead"
#ifndef XP_MACOSX
<menuitem label="&markThreadAsReadCmd.label;" accesskey="&markThreadAsReadCmd.accesskey;" observes="cmd_markThreadAsRead"
ifndef="" XP_MACOSX=""
key="key_markThreadAsRead"
#endif
endif=""
/>
<menuitem label="&markReadByDateCmd.label;" accesskey="&markReadByDateCmd.accesskey;" command="cmd_markReadByDate"
#ifndef XP_MACOSX
<menuitem label="&markReadByDateCmd.label;" accesskey="&markReadByDateCmd.accesskey;" command="cmd_markReadByDate"
ifndef="" XP_MACOSX=""
key="key_markReadByDate"
#endif
endif=""
/>
<menuitem label="&markAllReadCmd.label;" key="key_markAllRead" accesskey="&markAllReadCmd.accesskey;" observes="cmd_markAllRead"/>
<menuseparator/>
<menuitem id="markFlaggedMenuItem"
type="checkbox"
label="&markFlaggedCmd.label;"
accesskey="&markFlaggedCmd.accesskey;"
observes="cmd_markAsFlagged"
#ifndef XP_MACOSX
<menuitem label="&markAllReadCmd.label;" key="key_markAllRead" accesskey="&markAllReadCmd.accesskey;" observes="cmd_markAllRead"/>
<menuseparator/>
<menuitem id="markFlaggedMenuItem"
type="checkbox"
label="&markFlaggedCmd.label;"
accesskey="&markFlaggedCmd.accesskey;"
observes="cmd_markAsFlagged"
ifndef="" XP_MACOSX=""
key="key_toggleFlagged"
#endif
endif=""
/>
<menuseparator/>
<menuitem label="&markAsJunkCmd.label;"
accesskey="&markAsJunkCmd.accesskey;"
observes="cmd_markAsJunk"
#ifndef XP_MACOSX
<menuseparator/>
<menuitem label="&markAsJunkCmd.label;"
accesskey="&markAsJunkCmd.accesskey;"
observes="cmd_markAsJunk"
ifndef="" XP_MACOSX=""
key="key_markJunk"
#endif
endif=""
/>
<menuitem label="&markAsNotJunkCmd.label;"
key="key_markNotJunk"
<menuitem label="&markAsNotJunkCmd.label;"
key="key_markNotJunk"
accesskey="&markAsNotJunkCmd.accesskey;"
observes="cmd_markAsNotJunk"/>
<menuitem label="&recalculateJunkScoreCmd.label;"

View File

@ -1053,24 +1053,6 @@ var MessageWindowController =
case "cmd_recalculateJunkScore":
analyzeMessagesForJunk();
return;
case "cmd_label0":
gDBView.doCommand(nsMsgViewCommandType.label0);
return;
case "cmd_label1":
gDBView.doCommand(nsMsgViewCommandType.label1);
return;
case "cmd_label2":
gDBView.doCommand(nsMsgViewCommandType.label2);
return;
case "cmd_label3":
gDBView.doCommand(nsMsgViewCommandType.label3);
return;
case "cmd_label4":
gDBView.doCommand(nsMsgViewCommandType.label4);
return;
case "cmd_label5":
gDBView.doCommand(nsMsgViewCommandType.label5);
return;
case "cmd_downloadFlagged":
MsgDownloadFlagged();
return;

View File

@ -112,8 +112,8 @@ searchterm {
-moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder");
}
.ruleactiontarget[type="labelmessageas"] {
-moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-label");
.ruleactiontarget[type="addtagtomessage"] {
-moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-tag");
}
.ruleactiontarget[type="setpriorityto"] {

View File

@ -68,6 +68,8 @@ messenger.jar:
content/messenger/shareglue.js (/mailnews/base/resources/content/shareglue.js)
content/messenger/newFolderDialog.xul (/mailnews/base/resources/content/newFolderDialog.xul)
content/messenger/newFolderDialog.js (/mailnews/base/resources/content/newFolderDialog.js)
content/messenger/newTagDialog.xul (/mailnews/base/resources/content/newTagDialog.xul)
content/messenger/newTagDialog.js (/mailnews/base/resources/content/newTagDialog.js)
content/messenger/msgViewNavigation.js (/mailnews/base/resources/content/msgViewNavigation.js)
content/messenger/msgAccountCentral.xul (/mailnews/base/resources/content/msgAccountCentral.xul)
content/messenger/msgAccountCentral.js (/mailnews/base/resources/content/msgAccountCentral.js)

View File

@ -389,6 +389,7 @@ FeedItem.prototype =
openingLine +
'X-Mozilla-Status: 0000\n' +
'X-Mozilla-Status2: 00000000\n' +
'X-Mozilla-Keys: \n' +
'Date: ' + this.mDate + '\n' +
'Message-Id: <' + this.messageID + '>\n' +
'From: ' + this.author + '\n' +

View File

@ -43,6 +43,7 @@
<!ENTITY markMessageFlagged.label "Mark As Flagged">
<!ENTITY labelMessage.label "Label Message As">
<!ENTITY setPriority.label "Set Priority to">
<!ENTITY addTag.label "Tag Message">
<!ENTITY setJunkScore.label "Set Junk Status to">
<!ENTITY deleteMessage.label "Delete Message">
<!ENTITY deleteFromPOP.label "Delete From POP Server">

View File

@ -330,6 +330,8 @@
<!ENTITY fileHereMenu.accesskey "F">
<!ENTITY copyHereMenu.label "Copy Here">
<!ENTITY copyHereMenu.accesskey "C">
<!ENTITY tagMenu.label "Tag">
<!ENTITY tagMenu.accesskey "g">
<!ENTITY labelMenu.label "Label">
<!ENTITY labelMenu.accesskey "L">
<!ENTITY labelCmd0.accesskey "0">

View File

@ -48,6 +48,7 @@ newSubfolderMenuItem=Subfolder...
newFolder=New Folder...
newSubfolder=New Subfolder...
folderProperties=Folder Properties
newTag=New Tag...
getNextNMessages=Get Next %S News Messages
advanceNextPrompt=Advance to next unread message in %S?
titleNewsPreHost=on
@ -234,6 +235,9 @@ junk=Junk
# for the has attachment picker in search and mail views
hasAttachments=Has Attachments
# for the Tag picker in search and mail views.
tag=Tags
# mailnews.js
mailnews.send_default_charset=ISO-8859-1
mailnews.view_default_charset=ISO-8859-1
@ -361,6 +365,9 @@ passwordTitle=Mail Server Password Required
openWindowWarningTitle=Confirm
openWindowWarningText=Opening %S messages may be slow. Continue?
# for warning the user that a tag they're trying to create already exists
tagExists=A tag with that name already exists.
# for the virtual folder list dialog title
# %S is the name of the saved search folder
editVirtualFolderPropertiesTitle=Edit Saved Search Properties for %S

View File

@ -0,0 +1,38 @@
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is new tag dialog
-
- The Initial Developer of the Original Code is
- The Mozilla Corporation.
- Portions created by the Initial Developer are Copyright (C) 2006
- the Initial Developer. All Rights Reserved.
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<!-- Labels -->
<!ENTITY newTagDialog.title "New Tag">
<!ENTITY name.label "Tag:">
<!ENTITY name.accesskey "T">

View File

@ -15,7 +15,7 @@
12=Folder Info
13=Size
14=AnyText
15=Keywords
15=Tags
# for AB and LDAP
16=Any Name
17=Display Name
@ -50,5 +50,5 @@
46=Attachment Status
47=Junk Status
48=Label
49=Customize
# don't use above 49
49=Customize...

View File

@ -33,6 +33,7 @@
locale/@AB_CD@/messenger/threadpane.dtd (%chrome/messenger/threadpane.dtd)
locale/@AB_CD@/messenger/folderpane.dtd (%chrome/messenger/folderpane.dtd)
locale/@AB_CD@/messenger/newFolderDialog.dtd (%chrome/messenger/newFolderDialog.dtd)
locale/@AB_CD@/messenger/newTagDialog.dtd (%chrome/messenger/newTagDialog.dtd)
locale/@AB_CD@/messenger/renameFolderDialog.dtd (%chrome/messenger/renameFolderDialog.dtd)
locale/@AB_CD@/messenger/folderProps.dtd (%chrome/messenger/folderProps.dtd)
locale/@AB_CD@/messenger/subscribe.dtd (%chrome/messenger/subscribe.dtd)

View File

@ -70,6 +70,7 @@ XPIDLSRCS = \
nsIMsgMailSession.idl \
nsIMsgMessageService.idl \
nsIMsgSignature.idl \
nsIMsgTagService.idl \
nsIMsgThread.idl \
nsIUrlListener.idl \
nsIUrlListenerManager.idl \

View File

@ -70,7 +70,7 @@ typedef long nsMsgBiffState;
// enumerated type for determining if a message has been replied to, forwarded, etc.
typedef long nsMsgDispositionState;
[scriptable, uuid(28424d1c-db6f-4ac5-bc5d-418dd336120b)]
[scriptable, uuid(5711cb97-42ad-466d-9e8f-48b84a8b76a2)]
interface nsIMsgFolder : nsICollection {
const nsMsgBiffState nsMsgBiffState_NewMail = 0; // User has new mail waiting.
@ -497,4 +497,9 @@ const nsMsgBiffState nsMsgBiffState_Unknown = 2; // We dunno whether there is ne
in unsigned long aNumKeys, in boolean aLocalOnly,
in nsIUrlListener aUrlListener, out boolean aAsyncResults);
// used to set/clear tags - we could have a single method to setKeywords which
// would figure out the diffs, but these methods might be more convenient.
void addKeywordToMessages(in nsISupportsArray aMessages, in string aKeyword);
void removeKeywordFromMessages(in nsISupportsArray aMessages, in string aKeyword);
};

View File

@ -0,0 +1,72 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* The Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* David Bienvenu <bienvenu@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
#include "nsIStringEnumerator.idl"
/*
* Keys are the internal representation of tags, and use a limited range of
* characters, basically the characters allowed in imap keywords, which are
* alphanumeric characters, but don't include spaces. Keys are stored on
* the imap server, in local mail messages, and in summary files.
*
* Tags are the user visible representation of keys, and are full unicode
* strings. Tags should allow any unicode character.
*
* This service will do the mapping between keys and tags. When a tag
* is added, we'll need to "compute" the corresponding key to use. This
* will probably entail replacing illegal ascii characters (' ', '/', etc)
* with '_' and then converting to imap mod utf7. We'll then need to make
* sure that no other keyword has the same value since that algorithm
* doesn't guarantee a unique mapping.
*
*/
[scriptable, uuid(ad04db53-cfcc-47eb-b409-b24b3a0b6130)]
interface nsIMsgTagService : nsISupports {
AString getTagForKey(in ACString key); // look up the tag for a key.
void setTagForKey(in ACString key, in AString tag); // this can be used to "rename" a tag
ACString getKeyForTag(in AString tag); // get the key representation of a given tag
void addTagForKey(in ACString key, in AString tag, in ACString color);
void addTag(in AString tag, in ACString color);
ACString getColorForKey(in ACString key);
void deleteTag(in AString tag);
// we need some way to enumerate all tags. Or return a list of all tags.
readonly attribute nsIStringEnumerator tagEnumerator;
readonly attribute nsIUTF8StringEnumerator keyEnumerator;
};

View File

@ -516,6 +516,16 @@
{0x9c, 0xff, 0x2a, 0x66, 0x33, 0x33, 0x3b, 0x2b }}
//
// nsMsgTagService
//
#define NS_MSGTAGSERVICE_CONTRACTID \
"@mozilla.org/messenger/tagservice;1"
#define NS_MSGTAGSERVICE_CID \
{ /* ad04db53-cfcc-47eb-b409-b24b3a0b6130 */ \
0xad04db53, 0xcfcc, 0x47eb, \
{ 0xb4, 0x09, 0xb2, 0x4b, 0x3a, 0x0b, 0x61, 0x30}}
//
// nsMessengerOSIntegration
//
#define NS_MESSENGEROSINTEGRATION_CONTRACTID \

View File

@ -64,4 +64,7 @@
/* Provide a common means of detecting empty lines in a message. i.e. to detect the end of headers among other things...*/
#define EMPTY_MESSAGE_LINE(buf) (buf[0] == nsCRT::CR || buf[0] == nsCRT::LF || buf[0] == '\0')
/* blank filled header to store keyword/tags in the mailbox */
#define X_MOZILLA_KEYWORDS HEADER_X_MOZILLA_KEYWORDS ": " MSG_LINEBREAK
#endif

View File

@ -1235,7 +1235,8 @@
<!-- searchvalue - a widget which dynamically changes its user interface
depending on what type of data it's supposed to be showing
currently handles arbitrary text entry, and menulists for
priority, status, junk status, hasAttachment status, and addressbook
priority, status, junk status, tags, hasAttachment status,
and addressbook
-->
<binding id="searchvalue" name="searchValue">
<content>
@ -1277,12 +1278,6 @@
</xul:menulist>
<xul:menulist flex="1" class="search-value-menulist">
<xul:menupopup class="search-value-popup">
<xul:menuitem value="0" class="search-value-menuitem"/>
<xul:menuitem value="1" class="search-value-menuitem"/>
<xul:menuitem value="2" class="search-value-menuitem"/>
<xul:menuitem value="3" class="search-value-menuitem"/>
<xul:menuitem value="4" class="search-value-menuitem"/>
<xul:menuitem value="5" class="search-value-menuitem"/>
</xul:menupopup>
</xul:menulist>
<xul:menulist flex="1" class="search-value-menulist">
@ -1375,12 +1370,7 @@
// it's a text search, so show the textbox
this.setAttribute("selectedIndex", "0");
}
else if (val == Components.interfaces.nsMsgSearchAttrib.Label)
{
var children = document.getAnonymousNodes(this);
var abs = children[5].getElementsByAttribute("value", "1");
if (abs.item(0))
children[5].selectedItem = abs[0];
else if (val == Components.interfaces.nsMsgSearchAttrib.Keywords) {
this.setAttribute("selectedIndex", "5");
}
else if (val == Components.interfaces.nsMsgSearchAttrib.JunkStatus) {
@ -1433,11 +1423,14 @@
else
children[0].value = val.str;
}
else if (attrib == nsMsgSearchAttrib.Label)
else if (attrib == nsMsgSearchAttrib.Keywords)
{
var labelVal = children[5].getElementsByAttribute("value", val.label);
if (labelVal.item(0))
children[5].selectedItem = labelVal[0];
var keywordVal = children[5].getElementsByAttribute("value", val.str);
if (keywordVal.item(0))
{
children[5].value = val.str;
children[5].selectedItem = keywordVal[0];
}
}
else if (attrib == nsMsgSearchAttrib.JunkStatus) {
var junkStatus =
@ -1485,8 +1478,10 @@
else
searchValue.str = children[0].value;
}
else if (searchAttribute == nsMsgSearchAttrib.Label)
searchValue.label = children[5].selectedItem.value;
else if (searchAttribute == nsMsgSearchAttrib.Keywords)
{
searchValue.str = children[5].value;
}
else if (searchAttribute == nsMsgSearchAttrib.JunkStatus)
searchValue.junkStatus = children[6].value;
else if (searchAttribute == nsMsgSearchAttrib.Size)
@ -1507,6 +1502,33 @@
]]>
</body>
</method>
<method name="fillInTags">
<body>
<![CDATA[
var children = document.getAnonymousNodes(this);
var popupMenu = children[5].firstChild;
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"].getService(Components.interfaces.nsIMsgTagService);
var allTags = tagService.tagEnumerator;
var allKeys = tagService.keyEnumerator;
var tagNum = 0;
while (allTags.hasMore())
{
var tag = allTags.getNext();
var key = allKeys.getNext();
var newMenuItem = document.createElement('menuitem');
newMenuItem.setAttribute('label', tag);
newMenuItem.setAttribute('value', key);
popupMenu.appendChild(newMenuItem);
if (tagNum == 0)
{
children[5].selectedItem = newMenuItem;
children[5].value = key;
tagNum++;
}
}
]]>
</body>
</method>
<method name="fillStringsForChildren">
<parameter name="parentNode"/>
<parameter name="bundle"/>
@ -1565,18 +1587,14 @@
// initialize the address book picker
this.initialize(document.getAnonymousNodes(this)[4], bundle);
// initialize the label picker....
var labelStrings = GetLabelStrings();
var children = document.getAnonymousNodes(this)[5].firstChild.childNodes;
// set the label string on each label element...
for (var index = 0; index < 6; index++)
children[index].setAttribute('label', labelStrings[index]);
// initialize the junk status picker
this.initialize(document.getAnonymousNodes(this)[6], bundle);
// initialize the has attachment status picker
this.initialize(document.getAnonymousNodes(this)[7], bundle);
// initialize the tag list
fillInTags();
]]>
</constructor>
</implementation>

View File

@ -526,61 +526,6 @@ function SetMenuItemLabel(menuItemId, customLabel)
menuItem.setAttribute('label', customLabel);
}
function InitMessageLabel(menuType)
{
/* this code gets the label strings and changes the menu labels */
var color;
try
{
var isChecked = true;
var checkedLabel = gDBView.hdrForFirstSelectedMessage.label;
}
catch(ex)
{
isChecked = false;
}
for (var label = 0; label <= 5; label++)
{
try
{
var prefString = gPrefBranch.getComplexValue("mailnews.labels.description." + label,
Components.interfaces.nsIPrefLocalizedString);
var formattedPrefString = gMessengerBundle.getFormattedString("labelMenuItemFormat" + label,
[prefString], 1);
var menuItemId = menuType + "-labelMenuItem" + label;
var menuItem = document.getElementById(menuItemId);
SetMenuItemLabel(menuItemId, formattedPrefString);
if (isChecked && label == checkedLabel)
menuItem.setAttribute("checked", "true");
else
menuItem.setAttribute("checked", "false");
// commented out for now until UE decides on how to show the Labels menu items.
// This code will color either the text or background for the Labels menu items.
/*****
if (label != 0)
{
color = gPrefBranch.getCharPref("mailnews.labels.color." + label);
// this colors the text of the menuitem only.
//menuItem.setAttribute("style", ("color: " + color));
// this colors the background of the menuitem and
// when selected, text becomes white.
//menuItem.setAttribute("style", ("color: #FFFFFF"));
//menuItem.setAttribute("style", ("background-color: " + color));
}
****/
}
catch(ex)
{
}
}
document.commandDispatcher.updateCommands('create-menu-label');
}
function InitMessageMark()
{
var areMessagesRead = SelectedMessagesAreRead();

View File

@ -108,8 +108,8 @@ searchterm {
-moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder");
}
.ruleactiontarget[type="labelmessageas"] {
-moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-label");
.ruleactiontarget[type="addtagtomessage"] {
-moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-tag");
}
.ruleactiontarget[type="setpriorityto"] {

View File

@ -0,0 +1,90 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the new tag dialog
*
* The Initial Developer of the Original Code is
* David Bienvenu.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* David Bienvenu <bienvenu@nventure.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
var dialog;
function onLoad()
{
var arguments = window.arguments[0];
dialog = {};
dialog.OKButton = document.documentElement.getButton("accept");
dialog.nameField = document.getElementById("name");
dialog.nameField.focus();
// call this when OK is pressed
dialog.okCallback = arguments.okCallback;
moveToAlertPosition();
doEnabling();
}
function onOK()
{
var name = dialog.nameField.value;
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"].getService(Components.interfaces.nsIMsgTagService);
// do name validity check? Has to be non-empty, and not existing already
try
{
var key = tagService.getKeyForTag(name);
// above will throw an error if tag doesn't exist. So if it doesn't throw an error,
// the tag exists, so alert the user and return false.
}
catch (ex) {return dialog.okCallback(name, "")}
var messengerBundle = document.getElementById("bundle_messenger");
var alertText = messengerBundle.getString("tagExists");
window.alert(alertText);
return false;
}
function doEnabling()
{
if (dialog.nameField.value) {
if (dialog.OKButton.disabled)
dialog.OKButton.disabled = false;
} else {
if (!dialog.OKButton.disabled)
dialog.OKButton.disabled = true;
}
}

View File

@ -0,0 +1,58 @@
<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Mozilla Mail Tag Support
-
- The Initial Developer of the Original Code is
- The Mozilla Corporation.
- Portions created by the Initial Developer are Copyright (C) 2006
- the Initial Developer. All Rights Reserved.
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/newTagDialog.dtd">
<dialog xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&newTagDialog.title;"
onload="onLoad();"
ondialogaccept="return onOK();">
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
</stringbundleset>
<script type="application/x-javascript" src="chrome://messenger/content/newTagDialog.js"/>
<label value="&name.label;" accesskey="&name.accesskey;" control="name"/>
<textbox id="name" oninput="doEnabling();"/>
<separator/>
</dialog>

View File

@ -236,6 +236,9 @@ junk=Junk
# for the has attachment picker in search and mail views
hasAttachments=Has Attachments
# for the Tag picker in search and mail views.
tag=Tag
# mailnews.js
mailnews.send_default_charset=ISO-8859-1
mailnews.view_default_charset=ISO-8859-1

View File

@ -0,0 +1,38 @@
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is new tag dialog
-
- The Initial Developer of the Original Code is
- The Mozilla Corporation.
- Portions created by the Initial Developer are Copyright (C) 2006
- the Initial Developer. All Rights Reserved.
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<!-- Labels -->
<!ENTITY newTagDialog.title "New Tag">
<!ENTITY name.label "Tag:">
<!ENTITY name.accesskey "T">

View File

@ -46,7 +46,7 @@ interface nsIMsgSearchScopeTerm;
native nsCStringRef(nsCString&);
[scriptable, uuid(cc7795ce-1dd1-11b2-9ad2-dfa3c0b6ee09)]
[scriptable, uuid(cde583fe-9add-4adb-9e1a-9cfe050d8a26)]
interface nsIMsgSearchTerm : nsISupports {
attribute nsMsgSearchAttribValue attrib;
attribute nsMsgSearchOpValue op;
@ -93,6 +93,7 @@ interface nsIMsgSearchTerm : nsISupports {
readonly attribute boolean matchAllBeforeDeciding;
readonly attribute ACString termAsString;
boolean matchKeyword(in string keyword); // used for tag searches
attribute boolean matchAll;
};

View File

@ -59,7 +59,7 @@ typedef long nsMsgFilterIndex;
typedef long nsMsgRuleActionType;
[scriptable, uuid(3099cee1-eb76-4cd3-a40e-cfc8d2ef4437)]
[scriptable, uuid(59af7696-1e28-4642-a400-fa327ae0b8d8)]
interface nsMsgFilterAction {
/* these longs are all actually of type nsMsgFilterActionType */
@ -80,5 +80,6 @@ interface nsMsgFilterAction {
const long JunkScore=14;
const long FetchBodyFromPop3Server=15;
const long CopyToFolder=16;
const long AddTag=17;
};

View File

@ -80,13 +80,13 @@ interface nsMsgSearchAttrib {
const nsMsgSearchAttribValue CC = 7;
const nsMsgSearchAttribValue ToOrCC = 8;
const nsMsgSearchAttribValue Location = 9; /* result list only */
const nsMsgSearchAttribValue Location = 9; /* result list only */
const nsMsgSearchAttribValue MessageKey = 10; /* message result elems */
const nsMsgSearchAttribValue AgeInDays = 11;
const nsMsgSearchAttribValue AgeInDays = 11;
const nsMsgSearchAttribValue FolderInfo = 12; /* for "view thread context" from result */
const nsMsgSearchAttribValue Size = 13;
const nsMsgSearchAttribValue AnyText = 14;
const nsMsgSearchAttribValue Keywords = 15;
const nsMsgSearchAttribValue Keywords = 15; // keywords are the internal representation of tags.
const nsMsgSearchAttribValue Name = 16;
const nsMsgSearchAttribValue DisplayName = 17;
@ -106,12 +106,12 @@ interface nsMsgSearchAttrib {
const nsMsgSearchAttribValue Organization = 31;
const nsMsgSearchAttribValue Department = 32;
// 33 - 45, reserved for ab / LDAP;
const nsMsgSearchAttribValue HasAttachmentStatus = 46;
const nsMsgSearchAttribValue JunkStatus = 47;
// 33 - 45, reserved for ab / LDAP;
const nsMsgSearchAttribValue HasAttachmentStatus = 46;
const nsMsgSearchAttribValue JunkStatus = 47;
const nsMsgSearchAttribValue Label = 48; /* mail only...can search by label */
//49 is for showing customize... in ui headers start from 50 onwards up until 99.
const nsMsgSearchAttribValue OtherHeader = 49; /* for mail and news. MUST ALWAYS BE LAST attribute since we can have an arbitrary # of these... */
const nsMsgSearchAttribValue OtherHeader = 49; /* for mail and news. MUST ALWAYS BE LAST attribute since we can have an arbitrary # of these... */
const nsMsgSearchAttribValue kNumMsgSearchAttributes = 100; /* must be last attribute */
};

View File

@ -57,9 +57,9 @@ var gFilterActionList;
var gFilterActionStrings = ["none", "movemessage", "setpriorityto", "deletemessage",
"markasread", "ignorethread", "watchthread", "markasflagged",
"labelmessageas", "replytomessage", "forwardmessage", "stopexecution",
"label", "replytomessage", "forwardmessage", "stopexecution",
"deletefrompopserver", "leaveonpopserver", "setjunkscore",
"fetchfrompopserver", "copymessage"];
"fetchfrompopserver", "copymessage", "addtagtomessage"];
var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
@ -72,7 +72,7 @@ function filterEditorOnLoad()
gPrefBranch = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch(null);
gFilterBundle = document.getElementById("bundle_filter");
InitMessageLabel();
InitMessageMark();
if ("arguments" in window && window.arguments[0])
{
var args = window.arguments[0];

View File

@ -70,8 +70,8 @@
<xul:menuseparator/>
<xul:menuitem label="&markMessageRead.label;" value="markasread"/>
<xul:menuitem label="&markMessageFlagged.label;" value="markasflagged"/>
<xul:menuitem label="&labelMessage.label;" value="labelmessageas"/>
<xul:menuitem label="&setPriority.label;" value="setpriorityto"/>
<xul:menuitem label="&addTag.label;" value="addtagtomessage"/>
<xul:menuitem label="&setJunkScore.label;" value="setjunkscore" enablefornews="false"/>
<xul:menuseparator enableforpop3="true"/>
<xul:menuitem label="&deleteMessage.label;" value="deletemessage"/>
@ -255,6 +255,9 @@
case "setjunkscore":
document.getAnonymousNodes(actionTarget)[0].value = aFilterAction.junkScore;
break;
case "addtagtomessage":
document.getAnonymousNodes(actionTarget)[0].value = aFilterAction.strValue;
break;
default:
break;
}
@ -328,6 +331,7 @@
case "setjunkscore":
filterAction.junkScore = document.getAnonymousNodes(actionTarget)[0].value;
break;
case "addtagtomessage":
case "replytomessage":
case "forwardmessage":
filterAction.strValue = document.getAnonymousNodes(actionTarget)[0].value;
@ -385,16 +389,10 @@
</implementation>
</binding>
<binding id="ruleactiontarget-label" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<binding id="ruleactiontarget-tag" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
<xul:menuitem value="0"/>
<xul:menuitem value="1"/>
<xul:menuitem value="2"/>
<xul:menuitem value="3"/>
<xul:menuitem value="4"/>
<xul:menuitem value="5"/>
</xul:menupopup>
</xul:menulist>
</content>
@ -402,19 +400,21 @@
<implementation>
<constructor>
<![CDATA[
var menuItems = document.getAnonymousNodes(this)[0].menupopup.childNodes;
var prefBranch = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch(null);
for (var index = 0; index < menuItems.length; index++)
{
var menuEl = menuItems[index];
var prefString = prefBranch.getComplexValue("mailnews.labels.description." + menuEl.value,
Components.interfaces.nsIPrefLocalizedString);
menuEl.setAttribute("label", prefString);
}
// propagating a pre-existing hack to make the label get displayed correctly in the menulist
// now that we've changed the labels for each menu list. We need to use the current selectedIndex
var menuPopup = document.getAnonymousNodes(this)[0].menupopup;
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"].getService(Components.interfaces.nsIMsgTagService);
var allTags = tagService.tagEnumerator;
var allKeys = tagService.keyEnumerator;
while (allTags.hasMore())
{
var tag = allTags.getNext();
var key = allKeys.getNext();
var newMenuItem = document.createElement('menuitem');
newMenuItem.setAttribute('label', tag);
newMenuItem.setAttribute('value', key);
menuPopup.appendChild(newMenuItem);
}
// propagating a pre-existing hack to make the tag get displayed correctly in the menulist
// now that we've changed the tags for each menu list. We need to use the current selectedIndex
// (if its defined) to handle the case where we were initialized with a filter action already.
var currentItem = document.getAnonymousNodes(this)[0].selectedItem;
document.getAnonymousNodes(this)[0].selectedItem = null;

View File

@ -15,7 +15,7 @@
12=Folder Info
13=Size (KB)
14=AnyText
15=Keywords
15=Tags
# for AB and LDAP
16=Any Name
17=Display Name
@ -50,5 +50,5 @@
46=Attachment Status
47=Junk Status
48=Label
49=Customize
# don't use above 49
49=Customize...

View File

@ -730,6 +730,7 @@ nsresult nsMsgFilter::SaveRule(nsIOFileStream *aStream)
err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, junkScore, aStream);
}
break;
case nsMsgFilterAction::AddTag:
case nsMsgFilterAction::Reply:
case nsMsgFilterAction::Forward:
{
@ -818,6 +819,7 @@ static struct RuleActionsTableEntry ruleActionsTable[] =
{ nsMsgFilterAction::LeaveOnPop3Server, nsMsgFilterType::Inbox, 0, "Leave on Pop3 server"},
{ nsMsgFilterAction::JunkScore, nsMsgFilterType::All, 0, "JunkScore"},
{ nsMsgFilterAction::FetchBodyFromPop3Server, nsMsgFilterType::Inbox, 0, "Fetch body from Pop3Server"},
{ nsMsgFilterAction::AddTag, nsMsgFilterType::All, 0, "AddTag"},
};
const char *nsMsgFilter::GetActionStr(nsMsgRuleActionType action)

View File

@ -662,10 +662,16 @@ nsresult nsMsgFilterList::LoadTextFilters(nsIOFileStream *aStream)
}
else if (type == nsMsgFilterAction::Label)
{
// upgrade label to corresponding tag/keyword
PRInt32 res;
PRInt32 labelInt = value.ToInteger(&res, 10);
if (res == 0)
currentFilterAction->SetLabel((nsMsgLabelValue) labelInt);
{
nsCAutoString keyword("$label");
keyword.Append('0' + labelInt);
currentFilterAction->SetType(nsMsgFilterAction::AddTag);
currentFilterAction->SetStrValue(keyword.get());
}
}
else if (type == nsMsgFilterAction::JunkScore)
{
@ -674,7 +680,8 @@ nsresult nsMsgFilterList::LoadTextFilters(nsIOFileStream *aStream)
if (!res)
currentFilterAction->SetJunkScore(junkScore);
}
else if (type == nsMsgFilterAction::Forward || type == nsMsgFilterAction::Reply)
else if (type == nsMsgFilterAction::Forward || type == nsMsgFilterAction::Reply
|| type == nsMsgFilterAction::AddTag)
{
currentFilterAction->SetStrValue(value.get());
}

View File

@ -647,6 +647,13 @@ nsresult nsMsgFilterAfterTheFact::ApplyFilter()
m_curFolder->SetLabelForMessages(m_searchHitHdrs, filterLabel);
}
break;
case nsMsgFilterAction::AddTag:
{
nsXPIDLCString keyword;
filterAction->GetStrValue(getter_Copies(keyword));
m_curFolder->AddKeywordToMessages(m_searchHitHdrs, keyword.get());
}
break;
case nsMsgFilterAction::JunkScore:
{
nsCAutoString junkScoreStr;

View File

@ -197,11 +197,6 @@ nsMsgSearchValidityManager::InitOfflineMailTable()
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Label, nsMsgSearchOp::Is, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Label, nsMsgSearchOp::Is, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Label, nsMsgSearchOp::Isnt, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Label, nsMsgSearchOp::Isnt, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1);
@ -315,6 +310,14 @@ nsMsgSearchValidityManager::InitOfflineMailTable()
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
return rv;
}
@ -382,6 +385,15 @@ nsMsgSearchValidityManager::InitOnlineMailTable()
m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1);
m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1);
m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
m_onlineMailTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);

View File

@ -552,6 +552,13 @@ nsresult nsMsgSearchOfflineMail::ProcessSearchTerm(nsIMsgDBHdr *msgToMatch,
err = aTerm->MatchLabel(label, &result);
break;
}
case nsMsgSearchAttrib::Keywords:
{
nsXPIDLCString keywords;
msgToMatch->GetStringProperty("keywords", getter_Copies(keywords));
err = aTerm->MatchKeyword(keywords.get(), &result);
break;
}
case nsMsgSearchAttrib::JunkStatus:
{
nsXPIDLCString junkScoreStr;

View File

@ -500,7 +500,7 @@ nsresult nsMsgSearchAdapter::EncodeImapTerm (nsIMsgSearchTerm *term, PRBool real
whichMnemonic = m_kImapAnyText;
break;
case nsMsgSearchAttrib::Keywords:
whichMnemonic = m_kNntpKeywords;
whichMnemonic = m_kImapKeyword;
break;
case nsMsgSearchAttrib::MsgStatus:
useNot = PR_FALSE; // bizarrely, NOT SEEN is wrong, but UNSEEN is right.

View File

@ -97,6 +97,7 @@ nsMsgSearchAttribEntry SearchAttribEntryTable[] =
{nsMsgSearchAttrib::ToOrCC, "to or cc"},
{nsMsgSearchAttrib::AgeInDays, "age in days"},
{nsMsgSearchAttrib::Label, "label"},
{nsMsgSearchAttrib::Keywords, "tag"},
{nsMsgSearchAttrib::Size, "size"},
// this used to be nsMsgSearchAttrib::SenderInAddressBook
// we used to have two Sender menuitems
@ -663,8 +664,16 @@ nsresult nsMsgSearchTerm::DeStreamNew (char *inStream, PRInt16 /*length*/)
if (commaSep)
rv = ParseOperator(commaSep + 1, &m_operator);
NS_ENSURE_SUCCESS(rv, rv);
// convert label filters and saved searches to keyword equivalents
if (secondCommaSep)
ParseValue(secondCommaSep + 1);
if (m_attribute == nsMsgSearchAttrib::Label)
{
nsCAutoString keyword("$label");
m_value.attribute = m_attribute = nsMsgSearchAttrib::Keywords;
keyword.Append('0' + m_value.u.label);
m_value.string = PL_strdup(keyword.get());
}
return NS_OK;
}
@ -1212,38 +1221,38 @@ nsresult nsMsgSearchTerm::MatchAge (PRTime msgDate, PRBool *pResult)
nsresult nsMsgSearchTerm::MatchSize (PRUint32 sizeToMatch, PRBool *pResult)
{
NS_ENSURE_ARG_POINTER(pResult);
NS_ENSURE_ARG_POINTER(pResult);
PRBool result = PR_FALSE;
// We reduce the sizeToMatch rather than supplied size
// as then we can do an exact match on the displayed value
// which will be less confusing to the user.
PRUint32 sizeToMatchKB = sizeToMatch;
PRBool result = PR_FALSE;
// We reduce the sizeToMatch rather than supplied size
// as then we can do an exact match on the displayed value
// which will be less confusing to the user.
PRUint32 sizeToMatchKB = sizeToMatch;
if (sizeToMatchKB < 1024)
sizeToMatchKB = 1024;
if (sizeToMatchKB < 1024)
sizeToMatchKB = 1024;
sizeToMatchKB /= 1024;
sizeToMatchKB /= 1024;
switch (m_operator)
{
case nsMsgSearchOp::IsGreaterThan:
if (sizeToMatchKB > m_value.u.size)
result = PR_TRUE;
break;
case nsMsgSearchOp::IsLessThan:
if (sizeToMatchKB < m_value.u.size)
result = PR_TRUE;
break;
case nsMsgSearchOp::Is:
if (sizeToMatchKB == m_value.u.size)
result = PR_TRUE;
break;
default:
break;
}
*pResult = result;
return NS_OK;
switch (m_operator)
{
case nsMsgSearchOp::IsGreaterThan:
if (sizeToMatchKB > m_value.u.size)
result = PR_TRUE;
break;
case nsMsgSearchOp::IsLessThan:
if (sizeToMatchKB < m_value.u.size)
result = PR_TRUE;
break;
case nsMsgSearchOp::Is:
if (sizeToMatchKB == m_value.u.size)
result = PR_TRUE;
break;
default:
break;
}
*pResult = result;
return NS_OK;
}
nsresult nsMsgSearchTerm::MatchJunkStatus(const char *aJunkScore, PRBool *pResult)
@ -1269,22 +1278,22 @@ nsresult nsMsgSearchTerm::MatchJunkStatus(const char *aJunkScore, PRBool *pResul
}
nsresult rv = NS_OK;
PRBool matches = (junkStatus == m_value.u.junkStatus);
PRBool matches = (junkStatus == m_value.u.junkStatus);
switch (m_operator)
{
case nsMsgSearchOp::Is:
break;
case nsMsgSearchOp::Isnt:
matches = !matches;
break;
default:
rv = NS_ERROR_FAILURE;
NS_ASSERTION(PR_FALSE, "invalid compare op for junk status");
}
switch (m_operator)
{
case nsMsgSearchOp::Is:
break;
case nsMsgSearchOp::Isnt:
matches = !matches;
break;
default:
rv = NS_ERROR_FAILURE;
NS_ASSERTION(PR_FALSE, "invalid compare op for junk status");
}
*pResult = matches;
return rv;
return rv;
}
nsresult nsMsgSearchTerm::MatchLabel(nsMsgLabelValue aLabelValue, PRBool *pResult)
@ -1309,62 +1318,109 @@ nsresult nsMsgSearchTerm::MatchLabel(nsMsgLabelValue aLabelValue, PRBool *pResul
nsresult nsMsgSearchTerm::MatchStatus(PRUint32 statusToMatch, PRBool *pResult)
{
NS_ENSURE_ARG_POINTER(pResult);
NS_ENSURE_ARG_POINTER(pResult);
nsresult rv = NS_OK;
PRBool matches = (statusToMatch & m_value.u.msgStatus);
nsresult rv = NS_OK;
PRBool matches = (statusToMatch & m_value.u.msgStatus);
switch (m_operator)
{
case nsMsgSearchOp::Is:
break;
case nsMsgSearchOp::Isnt:
matches = !matches;
break;
default:
rv = NS_ERROR_FAILURE;
switch (m_operator)
{
case nsMsgSearchOp::Is:
break;
case nsMsgSearchOp::Isnt:
matches = !matches;
break;
default:
rv = NS_ERROR_FAILURE;
NS_ERROR("invalid compare op for msg status");
}
}
*pResult = matches;
return rv;
return rv;
}
nsresult nsMsgSearchTerm::MatchKeyword(const char *keyword, PRBool *pResult)
{
NS_ENSURE_ARG_POINTER(pResult);
nsresult rv = NS_OK;
nsCAutoString keys;
PRBool matches = PR_FALSE;
switch (m_operator)
{
case nsMsgSearchOp::Is:
matches = !strcmp(keyword, m_value.string);
break;
case nsMsgSearchOp::Isnt:
matches = strcmp(keyword, m_value.string);
break;
case nsMsgSearchOp::DoesntContain:
case nsMsgSearchOp::Contains:
{
const char *keywordLoc = PL_strstr(keyword, m_value.string);
const char *startOfKeyword = keyword;
PRUint32 keywordLen = strlen(keyword);
while (keywordLoc)
{
// if the keyword is at the beginning of the string, then it's a match if
// it is either the whole string, or is followed by a space, it's a match.
if (keywordLoc == startOfKeyword || (keywordLoc[-1] == ' '))
{
matches = keywordLen == strlen(keywordLoc) || (keywordLoc[keywordLen] == ' ');
if (matches)
break;
}
startOfKeyword = keywordLoc + keywordLen;
keywordLoc = PL_strstr(keyword, keywordLoc + keywordLen + 1);
}
}
break;
default:
rv = NS_ERROR_FAILURE;
NS_ERROR("invalid compare op for msg status");
}
*pResult = (m_operator == nsMsgSearchOp::DoesntContain) ? !matches : matches;
return rv;
}
nsresult
nsMsgSearchTerm::MatchPriority (nsMsgPriorityValue priorityToMatch,
PRBool *pResult)
{
NS_ENSURE_ARG_POINTER(pResult);
NS_ENSURE_ARG_POINTER(pResult);
nsresult err = NS_OK;
PRBool result=NS_OK;
nsresult err = NS_OK;
PRBool result=NS_OK;
// Use this ugly little hack to get around the fact that enums don't have
// integer compare operators
int p1 = (priorityToMatch == nsMsgPriority::none) ? (int) nsMsgPriority::normal : (int) priorityToMatch;
int p2 = (int) m_value.u.priority;
// Use this ugly little hack to get around the fact that enums don't have
// integer compare operators
int p1 = (priorityToMatch == nsMsgPriority::none) ? (int) nsMsgPriority::normal : (int) priorityToMatch;
int p2 = (int) m_value.u.priority;
switch (m_operator)
{
case nsMsgSearchOp::IsHigherThan:
if (p1 > p2)
result = PR_TRUE;
break;
case nsMsgSearchOp::IsLowerThan:
if (p1 < p2)
result = PR_TRUE;
break;
case nsMsgSearchOp::Is:
if (p1 == p2)
result = PR_TRUE;
break;
default:
result = PR_FALSE;
err = NS_ERROR_FAILURE;
NS_ASSERTION(PR_FALSE, "invalid match operator");
}
*pResult = result;
return err;
switch (m_operator)
{
case nsMsgSearchOp::IsHigherThan:
if (p1 > p2)
result = PR_TRUE;
break;
case nsMsgSearchOp::IsLowerThan:
if (p1 < p2)
result = PR_TRUE;
break;
case nsMsgSearchOp::Is:
if (p1 == p2)
result = PR_TRUE;
break;
default:
result = PR_FALSE;
err = NS_ERROR_FAILURE;
NS_ASSERTION(PR_FALSE, "invalid match operator");
}
*pResult = result;
return err;
}
// Lazily initialize the rfc822 header parser we're going to use to do

View File

@ -99,6 +99,8 @@ nsMsgSearchValueImpl::GetStr(PRUnichar** aResult)
NS_IMETHODIMP
nsMsgSearchValueImpl::SetStr(const PRUnichar* aValue)
{
if (!aValue)
return NS_ERROR_NULL_POINTER;
NS_ENSURE_TRUE(IS_STRING_ATTRIBUTE(mValue.attribute), NS_ERROR_ILLEGAL_VALUE);
if (mValue.string)
nsCRT::free(mValue.string);

View File

@ -127,6 +127,7 @@ CPPSRCS = \
nsSpamSettings.cpp \
nsCidProtocolHandler.cpp \
nsMsgContentPolicy.cpp \
nsMsgTagService.cpp\
$(NULL)
# MacOSX requires the MoreFiles module

View File

@ -769,14 +769,40 @@ nsresult nsMsgDBView::FetchPriority(nsIMsgDBHdr *aHdr, PRUnichar ** aPriorityStr
break;
}
if (priorityString)
*aPriorityString = nsCRT::strdup(priorityString);
else
*aPriorityString = nsnull;
*aPriorityString = (priorityString) ? nsCRT::strdup(priorityString) : nsnull;
return NS_OK;
}
nsresult nsMsgDBView::FetchTags(nsIMsgDBHdr *aHdr, PRUnichar ** aTagString)
{
nsresult rv = NS_OK;
if (!mTagService)
{
mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
nsXPIDLCString keywords;
nsXPIDLString label, tags;
FetchLabel(aHdr, getter_Copies(label));
aHdr->GetStringProperty("keywords", getter_Copies(keywords));
nsCStringArray keywordsArray;
keywordsArray.ParseString(keywords.get(), " ");
nsAutoString tag;
for (PRInt32 i = 0; i < keywordsArray.Count(); i++)
{
rv = mTagService->GetTagForKey(*(keywordsArray[i]), tag);
if (NS_SUCCEEDED(rv) && !tag.IsEmpty() && !tag.Equals(label))
{
if (!tags.IsEmpty())
tags.Append((PRUnichar) ' ');
tags.Append(tag);
}
}
tags.Append(label);
*aTagString = ToNewUnicode(tags);
return rv;
}
nsresult nsMsgDBView::FetchLabel(nsIMsgDBHdr *aHdr, PRUnichar ** aLabelString)
{
nsresult rv = NS_OK;
@ -1601,8 +1627,8 @@ NS_IMETHODIMP nsMsgDBView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAStr
rv = FetchPriority(msgHdr, getter_Copies(valueText));
aValue.Assign(valueText);
break;
case 'l': // label
rv = FetchLabel(msgHdr, getter_Copies(valueText));
case 'l': // label - labels are now tags...
rv = FetchTags(msgHdr, getter_Copies(valueText));
aValue.Assign(valueText);
break;
case 'a': // account

View File

@ -64,6 +64,7 @@
#include "nsIObserver.h"
#include "nsIMsgFilterPlugin.h"
#include "nsIStringBundle.h"
#include "nsMsgTagService.h"
#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
@ -171,6 +172,7 @@ protected:
nsresult FetchSize(nsIMsgDBHdr * aHdr, PRUnichar ** aSizeString);
nsresult FetchPriority(nsIMsgDBHdr *aHdr, PRUnichar ** aPriorityString);
nsresult FetchLabel(nsIMsgDBHdr *aHdr, PRUnichar ** aLabelString);
nsresult FetchTags(nsIMsgDBHdr *aHdr, PRUnichar ** aTagString);
nsresult FetchAccount(nsIMsgDBHdr * aHdr, PRUnichar ** aAccount);
nsresult CycleThreadedColumn(nsIDOMElement * aElement);
@ -358,6 +360,7 @@ protected:
// I18N date formater service which we'll want to cache locally.
nsCOMPtr<nsIDateTimeFormat> mDateFormater;
nsCOMPtr<nsIMsgHeaderParser> mHeaderParser;
nsCOMPtr<nsIMsgTagService> mTagService;
// i'm not sure if we are going to permamently need a nsIMessenger instance or if we'll be able
// to phase it out eventually....for now we need it though.
nsCOMPtr<nsIMessenger> mMessengerInstance;

View File

@ -55,7 +55,7 @@
#include "nsIInterfaceRequestorUtils.h"
#include "nsIMsgLocalMailFolder.h"
#include "nsIMsgImapMailFolder.h"
#include "nsMailHeaders.h"
#include "nsMsgI18N.h"
#include "prprf.h"
#include "nsMsgLocalFolderHdrs.h"
@ -584,6 +584,20 @@ done:
return rv;
}
void nsFolderCompactState::AdvanceToNextLine(const char *buffer, PRUint32 &bufferOffset, PRUint32 maxBufferOffset)
{
for (; bufferOffset < maxBufferOffset; bufferOffset++)
{
if (buffer[bufferOffset] == nsCRT::CR || buffer[bufferOffset] == nsCRT::LF)
{
bufferOffset++;
if (buffer[bufferOffset- 1] == nsCRT::CR && buffer[bufferOffset] == nsCRT::LF)
bufferOffset++;
break;
}
}
}
NS_IMETHODIMP
nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
nsIInputStream *inStr,
@ -594,9 +608,16 @@ nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
nsresult rv = NS_OK;
PRUint32 msgFlags;
PRBool checkForKeyword = m_startOfMsg;
PRBool addKeywordHdr = PR_FALSE;
PRUint32 needToGrowKeywords = 0;
PRUint32 statusOffset;
nsXPIDLCString msgHdrKeywords;
if (m_startOfMsg)
{
m_statusOffset = 0;
m_addedHeaderSize = 0;
m_messageUri.SetLength(0); // clear the previous message uri
if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray[m_curIndex],
m_messageUri)))
@ -606,7 +627,6 @@ nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
if (m_curSrcHdr)
{
(void) m_curSrcHdr->GetFlags(&msgFlags);
PRUint32 statusOffset;
(void) m_curSrcHdr->GetStatusOffset(&statusOffset);
if (statusOffset == 0)
m_needStatusLine = PR_TRUE;
@ -618,9 +638,21 @@ nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
while (NS_SUCCEEDED(rv) && (PRInt32) count > 0)
{
maxReadCount = count > 4096 ? 4096 : count;
writeCount = 0;
rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount);
if (NS_SUCCEEDED(rv))
{
if (checkForKeyword)
{
const char *keywordHdr = PL_strnrstr(m_dataBuffer, HEADER_X_MOZILLA_KEYWORDS, readCount);
if (keywordHdr)
m_curSrcHdr->GetUint32Property("growKeywords", &needToGrowKeywords);
else
addKeywordHdr = PR_TRUE;
checkForKeyword = PR_FALSE;
m_curSrcHdr->GetStringProperty("keywords", getter_Copies(msgHdrKeywords));
}
PRUint32 blockOffset = 0;
if (m_needStatusLine)
{
m_needStatusLine = PR_FALSE;
@ -630,26 +662,16 @@ nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
// in OnEndCopy).
if (!strncmp(m_dataBuffer, "From ", 5))
{
PRInt32 charIndex;
for (charIndex = 5; charIndex < readCount; charIndex++)
{
if (m_dataBuffer[charIndex] == nsCRT::CR || m_dataBuffer[charIndex] == nsCRT::LF)
{
charIndex++;
if (m_dataBuffer[charIndex- 1] == nsCRT::CR && m_dataBuffer[charIndex] == nsCRT::LF)
charIndex++;
break;
}
}
blockOffset = 5;
// skip from line
AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
char statusLine[50];
writeCount = m_fileStream->write(m_dataBuffer, charIndex);
m_statusOffset = charIndex;
writeCount = m_fileStream->write(m_dataBuffer, blockOffset);
m_statusOffset = blockOffset;
PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
m_statusLineSize = m_fileStream->write(statusLine, strlen(statusLine));
m_addedHeaderSize = m_fileStream->write(statusLine, strlen(statusLine));
PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
m_statusLineSize += m_fileStream->write(statusLine, strlen(statusLine));
writeCount += m_fileStream->write(m_dataBuffer + charIndex, readCount - charIndex);
m_addedHeaderSize += m_fileStream->write(statusLine, strlen(statusLine));
}
else
{
@ -665,10 +687,99 @@ nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
}
}
}
else
#define EXTRA_KEYWORD_HDR " "MSG_LINEBREAK
if (addKeywordHdr)
{
writeCount = m_fileStream->write(m_dataBuffer, readCount);
// if blockOffset is set, we added x-mozilla-status headers so
// file pointer is already past them.
if (!blockOffset)
{
blockOffset = statusOffset;
// skip x-mozilla-status and status2 lines.
AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
// need to rewrite the headers up to and including the x-mozilla-status2 header
writeCount = m_fileStream->write(m_dataBuffer, blockOffset);
}
// we should write out the existing keywords from the msg hdr, if any.
if (msgHdrKeywords.IsEmpty())
{ // no keywords, so write blank header
m_addedHeaderSize += m_fileStream->write(X_MOZILLA_KEYWORDS, sizeof(X_MOZILLA_KEYWORDS) - 1);
}
else
{
if (msgHdrKeywords.Length() < sizeof(X_MOZILLA_KEYWORDS) - sizeof(HEADER_X_MOZILLA_KEYWORDS) + 10 /* allow some slop */)
{ // keywords fit in normal blank header, so replace blanks in keyword hdr with keywords
nsCAutoString keywordsHdr(X_MOZILLA_KEYWORDS);
keywordsHdr.Replace(sizeof(HEADER_X_MOZILLA_KEYWORDS) + 1, msgHdrKeywords.Length(), msgHdrKeywords);
m_addedHeaderSize += m_fileStream->write(keywordsHdr.get(), keywordsHdr.Length());
}
else
{ // keywords don't fit, so write out keywords on one line and an extra blank line
nsCString newKeywordHeader(HEADER_X_MOZILLA_KEYWORDS ": ");
newKeywordHeader.Append(msgHdrKeywords);
newKeywordHeader.Append(MSG_LINEBREAK EXTRA_KEYWORD_HDR);
m_addedHeaderSize += m_fileStream->write(newKeywordHeader.get(), newKeywordHeader.Length());
}
}
addKeywordHdr = PR_FALSE;
}
else if (needToGrowKeywords)
{
blockOffset = statusOffset;
if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN))
AdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status hdr
if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN))
AdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status2 hdr
PRUint32 preKeywordBlockOffset = blockOffset;
if (!strncmp(m_dataBuffer + blockOffset, HEADER_X_MOZILLA_KEYWORDS, sizeof(HEADER_X_MOZILLA_KEYWORDS) - 1))
{
do
{
// skip x-mozilla-keywords hdr and any existing continuation headers
AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
}
while (m_dataBuffer[blockOffset] == ' ');
}
PRInt32 oldKeywordSize = blockOffset - preKeywordBlockOffset;
// rewrite the headers up to and including the x-mozilla-status2 header
writeCount = m_fileStream->write(m_dataBuffer, preKeywordBlockOffset);
// let's just rewrite all the keywords on several lines and add a blank line,
// instead of worrying about which are missing.
PRBool done = PR_FALSE;
nsCAutoString keywordHdr(HEADER_X_MOZILLA_KEYWORDS ": ");
PRInt32 nextBlankOffset = 0;
PRInt32 curHdrLineStart = 0;
PRInt32 newKeywordSize = 0;
while (!done)
{
nextBlankOffset = msgHdrKeywords.FindChar(' ', nextBlankOffset);
if (nextBlankOffset == kNotFound)
{
nextBlankOffset = msgHdrKeywords.Length();
done = PR_TRUE;
}
if (nextBlankOffset - curHdrLineStart > 90 || done)
{
keywordHdr.Append(nsDependentCSubstring(msgHdrKeywords, curHdrLineStart, msgHdrKeywords.Length() - curHdrLineStart));
keywordHdr.Append(MSG_LINEBREAK);
newKeywordSize += m_fileStream->write(keywordHdr.get(), keywordHdr.Length());
curHdrLineStart = nextBlankOffset;
keywordHdr.Assign(' ');
}
nextBlankOffset++;
}
newKeywordSize += m_fileStream->write(EXTRA_KEYWORD_HDR, sizeof(EXTRA_KEYWORD_HDR) - 1);
m_addedHeaderSize += newKeywordSize - oldKeywordSize;
m_curSrcHdr->SetUint32Property("growKeywords", 0);
needToGrowKeywords = PR_FALSE;
writeCount += blockOffset - preKeywordBlockOffset; // fudge writeCount
}
NS_ASSERTION(readCount > blockOffset, "bad block offset");
writeCount += m_fileStream->write(m_dataBuffer + blockOffset, readCount - blockOffset);
count -= readCount;
if (writeCount != readCount)
{
@ -860,12 +971,16 @@ nsFolderCompactState::EndCopy(nsISupports *url, nsresult aStatus)
m_db->CopyHdrFromExistingHdr(m_startOfNewMsg, m_curSrcHdr, PR_TRUE,
getter_AddRefs(newMsgHdr));
m_curSrcHdr = nsnull;
if (newMsgHdr && m_statusOffset != 0)
if (newMsgHdr)
{
PRUint32 oldMsgSize;
newMsgHdr->SetStatusOffset(m_statusOffset);
(void) newMsgHdr->GetMessageSize(&oldMsgSize);
newMsgHdr->SetMessageSize(oldMsgSize + m_statusLineSize);
if ( m_statusOffset != 0)
newMsgHdr->SetStatusOffset(m_statusOffset);
if (m_addedHeaderSize != 0)
{
PRUint32 oldMsgSize;
(void) newMsgHdr->GetMessageSize(&oldMsgSize);
newMsgHdr->SetMessageSize(oldMsgSize + m_addedHeaderSize);
}
}
// m_db->Commit(nsMsgDBCommitType::kLargeCommit); // no sense commiting until the end

View File

@ -79,6 +79,7 @@ protected:
void ShowCompactingStatusMsg();
void ShowDoneStatus();
nsresult CompactNextFolder();
void AdvanceToNextLine(const char *buffer, PRUint32 &bufferOffset, PRUint32 maxBufferOffset);
nsCString m_baseMessageUri; // base message uri
@ -106,7 +107,7 @@ protected:
PRBool m_needStatusLine;
PRBool m_startOfMsg;
PRInt32 m_statusOffset;
PRInt32 m_statusLineSize;
PRInt32 m_addedHeaderSize;
nsCOMPtr <nsISupportsArray> m_offlineFolderArray;
};

View File

@ -0,0 +1,329 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* David Bienvenu <bienvenu@mozilla.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "msgCore.h"
#include "nsMsgTagService.h"
#include "nsIPrefService.h"
#include "nsISupportsPrimitives.h"
#include "nsMsgI18N.h"
#include "nsIPrefLocalizedString.h"
#include "nsMsgDBView.h" // for labels migration
#include "nsStringEnumerator.h"
NS_IMPL_ISUPPORTS1(nsMsgTagService, nsIMsgTagService)
nsMsgTagService::nsMsgTagService()
{
m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
// need to figure out how to migrate the tags only once.
MigrateLabelsToTags();
}
nsMsgTagService::~nsMsgTagService()
{
/* destructor code */
}
/* wstring getTagForKey (in string key); */
NS_IMETHODIMP nsMsgTagService::GetTagForKey(const nsACString &key, nsAString &_retval)
{
nsCAutoString prefName("mailnews.tags.");
prefName.Append(key);
prefName.AppendLiteral(".tag");
return GetUnicharPref(prefName.get(), _retval);
}
/* void setTagForKey (in string key); */
NS_IMETHODIMP nsMsgTagService::SetTagForKey(const nsACString &key, const nsAString &tag )
{
nsCAutoString prefName("mailnews.tags.");
prefName.Append(key);
prefName.AppendLiteral(".tag");
return SetUnicharPref(prefName.get(), tag);
}
/* void getKeyForTag (in wstring tag); */
NS_IMETHODIMP nsMsgTagService::GetKeyForTag(const nsAString &aTag, nsACString &aKey)
{
PRUint32 count;
char **prefList;
nsresult rv = m_prefBranch->GetChildList("mailnews.tags.", &count, &prefList);
NS_ENSURE_SUCCESS(rv, rv);
// traverse the list, and look for a pref with the desired tag value.
for (PRUint32 i = count; i--; )
{
// The prefname we passed to GetChildList was of the form
// "mailnews.tags." and we are returned the descendants
// in the form of ""mailnews.tags.<key>.tag"
// But we only want the tags, so check that the string
// ends with tag.
if (StringEndsWith(nsDependentCString(prefList[i]), NS_LITERAL_CSTRING(".tag")))
{
nsAutoString curTag;
GetUnicharPref(prefList[i], curTag);
if (aTag.Equals(curTag))
{
aKey = Substring(nsDependentCString(prefList[i]), 14, strlen(prefList[i]) - 18);
break;
}
}
}
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, prefList);
return aKey.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
}
/* void addTagForKey (in string key, in wstring tag, in long color); */
NS_IMETHODIMP nsMsgTagService::AddTagForKey(const nsACString &key, const nsAString &tag, const nsACString &color)
{
nsCAutoString prefName("mailnews.tags.");
prefName.Append(key);
prefName.AppendLiteral(".tag");
SetUnicharPref(prefName.get(), tag);
prefName.Replace(prefName.Length() - 3, 3, NS_LITERAL_CSTRING("color"));
m_prefBranch->SetCharPref(prefName.get(), PromiseFlatCString(color).get());
return NS_OK;
}
/* void addTag (in wstring tag, in long color); */
NS_IMETHODIMP nsMsgTagService::AddTag(const nsAString &tag, const nsACString &color)
{
nsCAutoString prefName("mailnews.tags.");
// figure out key from tag. Apply transformation stripping out
// illegal characters like <SP> and then convert to imap mod utf7.
// Then, check if we have a tag with that key yet, and if so,
// make it unique by appending -1, -2, etc.
// Should we use an iterator?
nsAutoString transformedTag(tag);
transformedTag.ReplaceChar(" ()/{%*<>\\\"", '_');
nsCAutoString key;
CopyUTF16toMUTF7(transformedTag, key);
prefName.Append(key);
while (PR_TRUE)
{
nsAutoString tagValue;
GetUnicharPref(prefName.get(), tagValue);
if (tagValue.IsEmpty() || tagValue.Equals(tag))
return AddTagForKey(key, tag, color);
prefName.Append('A');
}
NS_ASSERTION(PR_FALSE, "can't get here");
return NS_ERROR_FAILURE;
}
/* long getColorForKey (in string key); */
NS_IMETHODIMP nsMsgTagService::GetColorForKey(const nsACString &key, nsACString &_retval)
{
nsCAutoString prefName("mailnews.tags.");
prefName.Append(key);
prefName.AppendLiteral(".color");
nsXPIDLCString color;
return m_prefBranch->GetCharPref(prefName.get(), getter_Copies(color));
_retval = color;
}
/* void deleteTag (in wstring tag); */
NS_IMETHODIMP nsMsgTagService::DeleteTag(const nsAString &tag)
{
// do we want to set a .deleted pref, or just set the tag
// property to "", or clear the pref(s)?
return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute nsIStringEnumerator tagEnumerator; */
NS_IMETHODIMP nsMsgTagService::GetTagEnumerator(nsIStringEnumerator * *aTagEnumerator)
{
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 count;
char **prefList;
rv = prefBranch->GetChildList("mailnews.tags.", &count, &prefList);
NS_ENSURE_SUCCESS(rv, rv);
nsStringArray *stringArray = new nsStringArray(count); // or should it be count / 2?
if (!stringArray)
return NS_ERROR_OUT_OF_MEMORY;
// traverse the list, and truncate all the descendant strings to just
// one branch level below the root branch.
for (PRUint32 i = count; i--; )
{
// The prefname we passed to GetChildList was of the form
// "mailnews.tags." and we are returned the descendants
// in the form of ""mailnews.tags.<key>.tag"
// But we only want the tags, so check that the string
// ends with tag.
if (StringEndsWith(nsDependentCString(prefList[i]), NS_LITERAL_CSTRING(".tag")))
{
nsAutoString tag;
GetUnicharPref(prefList[i], tag);
stringArray->AppendString(tag);
}
}
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, prefList);
return NS_NewAdoptingStringEnumerator(aTagEnumerator, stringArray);;
}
NS_IMETHODIMP nsMsgTagService::GetKeyEnumerator(nsIUTF8StringEnumerator * *aKeyEnumerator)
{
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 count;
char **prefList;
rv = prefBranch->GetChildList("mailnews.tags.", &count, &prefList);
NS_ENSURE_SUCCESS(rv, rv);
nsCStringArray *stringArray = new nsCStringArray; // or should it be count / 2?
if (!stringArray)
return NS_ERROR_OUT_OF_MEMORY;
// traverse the list, and truncate all the descendant strings to just
// one branch level below the root branch.
for (PRUint32 i = count; i--; )
{
// The prefname we passed to GetChildList was of the form
// "mailnews.tags." and we are returned the descendants
// in the form of ""mailnews.tags.<key>.tag"
// But we only want the keys, so check that the string
// ends with tag.
if (StringEndsWith(nsDependentCString(prefList[i]), NS_LITERAL_CSTRING(".tag")))
{
nsDependentCSubstring key(nsDependentCString(prefList[i]), 14, strlen(prefList[i]) - 18);
stringArray->AppendCString(key);
}
}
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, prefList);
return NS_NewAdoptingUTF8StringEnumerator(aKeyEnumerator, stringArray);;
}
/* End of implementation class template. */
nsresult
nsMsgTagService::getPrefService()
{
if (m_prefBranch)
return NS_OK;
nsresult rv;
m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv))
m_prefBranch = nsnull;
return rv;
}
nsresult nsMsgTagService::SetUnicharPref(const char *prefName,
const nsAString &val)
{
nsresult rv = getPrefService();
NS_ENSURE_SUCCESS(rv, rv);
if (!val.IsEmpty())
{
nsCOMPtr<nsISupportsString> supportsString =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
if (supportsString)
{
supportsString->SetData(val);
rv = m_prefBranch->SetComplexValue(prefName,
NS_GET_IID(nsISupportsString),
supportsString);
}
}
else
{
m_prefBranch->ClearUserPref(prefName);
}
return rv;
}
nsresult nsMsgTagService::GetUnicharPref(const char *prefName,
nsAString &prefValue)
{
nsresult rv = getPrefService();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsString> supportsString =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
if (supportsString)
{
rv = m_prefBranch->GetComplexValue(prefName,
NS_GET_IID(nsISupportsString),
getter_AddRefs(supportsString));
if (supportsString)
rv = supportsString->GetData(prefValue);
else
prefValue.Truncate();
}
return rv;
}
nsresult nsMsgTagService::MigrateLabelsToTags()
{
nsCString prefString;
PRInt32 prefVersion = 0;
nsresult rv = m_prefBranch->GetIntPref("mailnews.tags.version", &prefVersion);
if (NS_SUCCEEDED(rv) && prefVersion == 1)
return rv;
nsCOMPtr<nsIPrefLocalizedString> pls;
nsXPIDLString ucsval;
nsCAutoString labelKey("$label1");
for(PRInt32 i = 0; i < PREF_LABELS_MAX; )
{
prefString.Assign(PREF_LABELS_DESCRIPTION);
prefString.AppendInt(i + 1);
rv = m_prefBranch->GetComplexValue(prefString.get(), NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
NS_ENSURE_SUCCESS(rv, rv);
pls->ToString(getter_Copies(ucsval));
prefString.Assign(PREF_LABELS_COLOR);
prefString.AppendInt(i + 1);
nsXPIDLCString csval;
NS_ENSURE_SUCCESS(rv, rv);
rv = m_prefBranch->GetCharPref(prefString.get(), getter_Copies(csval));
NS_ENSURE_SUCCESS(rv, rv);
rv = AddTagForKey(labelKey, ucsval, csval);
labelKey.SetCharAt(++i + '1', 6);
}
m_prefBranch->SetIntPref("mailnews.tags.version", 1);
return rv;
}

View File

@ -0,0 +1,76 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* David Bienvenu <bienvenu@mozilla.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsMsgTagService_h__
#define nsMsgTagService_h__
#include "nsIMsgTagService.h"
#include "nsIPrefBranch.h"
class nsMsgTagEntry
{
public:
nsMsgTagEntry(const char *key, const PRUnichar *tag, PRUint32 color);
nsString m_tag;
nsCString m_key;
PRUint32 m_color;
};
class nsMsgTagService : public nsIMsgTagService
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMSGTAGSERVICE
nsMsgTagService();
private:
~nsMsgTagService();
protected:
nsresult getPrefService() ;
nsresult SetUnicharPref(const char *prefName,
const nsAString &prefValue);
nsresult GetUnicharPref(const char *prefName,
nsAString &prefValue);
nsresult MigrateLabelsToTags();
nsCOMPtr<nsIPrefBranch> m_prefBranch;
};
#endif

View File

@ -5250,3 +5250,79 @@ void nsMsgDBFolder::SetMRUTime()
SetStringProperty(MRU_TIME_PROPERTY, nowStr.get());
}
NS_IMETHODIMP nsMsgDBFolder::AddKeywordToMessages(nsISupportsArray *aMessages, const char *aKeyword)
{
nsresult rv = NS_OK;
GetDatabase(nsnull);
if (mDatabase)
{
PRUint32 count;
NS_ENSURE_ARG(aMessages);
nsresult rv = aMessages->Count(&count);
NS_ENSURE_SUCCESS(rv, rv);
nsXPIDLCString keywords;
for(PRUint32 i = 0; i < count; i++)
{
nsMsgKey msgKey;
nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
NS_ENSURE_SUCCESS(rv, rv);
(void) message->GetMessageKey(&msgKey);
message->GetStringProperty("keywords", getter_Copies(keywords));
nsACString::const_iterator start, end;
if (!MsgFindKeyword(nsDependentCString(aKeyword), keywords, start, end))
{
if (!keywords.IsEmpty())
keywords.Append(' ');
keywords.Append(aKeyword);
mDatabase->SetStringProperty(msgKey, "keywords", keywords);
}
}
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::RemoveKeywordFromMessages(nsISupportsArray *aMessages, const char *aKeyword)
{
nsresult rv = NS_OK;
GetDatabase(nsnull);
if (mDatabase)
{
PRUint32 count;
NS_ENSURE_ARG(aMessages);
nsresult rv = aMessages->Count(&count);
NS_ENSURE_SUCCESS(rv, rv);
nsXPIDLCString keywords;
// If the tag is also a label, we should remove the label too...
PRBool keywordIsLabel = (!strncmp(aKeyword, "$label", 6) && aKeyword[6] >= '1' && aKeyword[6] <= '5');
for(PRUint32 i = 0; i < count; i++)
{
nsMsgKey msgKey;
nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
NS_ENSURE_SUCCESS(rv, rv);
(void) message->GetMessageKey(&msgKey);
if (keywordIsLabel)
{
nsMsgLabelValue labelValue;
message->GetLabel(&labelValue);
if (labelValue == aKeyword[6])
message->SetLabel(0);
}
rv = message->GetStringProperty("keywords", getter_Copies(keywords));
nsACString::const_iterator start, end;
nsACString::const_iterator saveStart;
keywords.BeginReading(saveStart);
if (MsgFindKeyword(nsDependentCString(aKeyword), keywords, start, end))
{
keywords.Cut(Distance(saveStart, start), Distance(start, end));
mDatabase->SetStringProperty(msgKey, "keywords", keywords);
}
}
}
return rv;
}

View File

@ -73,10 +73,10 @@ class NS_MSG_BASE nsMsgLineBuffer : public nsMsgLineBufferHandler
public:
nsMsgLineBuffer(nsMsgLineBufferHandler *handler, PRBool convertNewlinesP);
virtual ~nsMsgLineBuffer();
PRInt32 BufferInput(const char *net_buffer, PRInt32 net_buffer_size);
virtual ~nsMsgLineBuffer();
PRInt32 BufferInput(const char *net_buffer, PRInt32 net_buffer_size);
// Not sure why anyone cares, by NNTPHost seems to want to know the buf pos.
PRUint32 GetBufferPos() {return m_bufferPos;}
PRUint32 GetBufferPos() {return m_bufferPos;}
virtual PRInt32 HandleLine(char *line, PRUint32 line_length);
// flush last line, though it won't be CRLF terminated.
@ -88,7 +88,7 @@ protected:
void SetLookingForCRLF(PRBool b);
nsMsgLineBufferHandler *m_handler;
PRBool m_convertNewlinesP;
PRBool m_convertNewlinesP;
PRBool m_lookingForCRLF;
};

View File

@ -1221,3 +1221,36 @@ void GetSummaryFileLocation(nsFileSpec& fileLocation, nsFileSpec* summaryLocatio
summaryLocation->SetLeafName(fileName);
}
PRBool MsgFindKeyword(nsACString &keyword, nsACString &keywords, nsACString::const_iterator &start, nsACString::const_iterator &end)
{
keywords.BeginReading(start);
keywords.EndReading(end);
if (*start == ' ')
start++;
nsACString::const_iterator saveStart(start), saveEnd(end);
while (PR_TRUE)
{
if (FindInReadable(keyword, start, end))
{
PRBool beginMatches = start == saveStart;
PRBool endMatches = end == saveEnd;
nsACString::const_iterator beforeStart(start);
beforeStart--;
// start and end point to the beginning and end of the match
if (beginMatches && (end == saveEnd || *end == ' ')
|| (endMatches && *beforeStart == ' ')
|| *beforeStart == ' ' && *end == ' ')
{
if (*end == ' ')
end++;
return PR_TRUE;
}
else
start = end; // advance past bogus match.
}
else
break;
}
return PR_FALSE;
}

View File

@ -158,6 +158,11 @@ NS_MSG_BASE nsresult GetSummaryFileLocation(nsIFileSpec* fileLocation,
// on bug 33451 to remove nsIFileSpec from mailnews.
NS_MSG_BASE void GetSummaryFileLocation(nsFileSpec& fileLocation,
nsFileSpec* summaryLocation);
// fills in the position of the passed in keyword in the passed in keyword list
// and returns false if the keyword isn't present
NS_MSG_BASE PRBool MsgFindKeyword(nsACString &keyword, nsACString &keywords,
nsACString::const_iterator &start,
nsACString::const_iterator &end);
#endif

View File

@ -103,6 +103,8 @@
#include "nsCidProtocolHandler.h"
#include "nsRssIncomingServer.h"
#include "nsRssService.h"
#include "nsMsgTagService.h"
#ifdef XP_WIN
#include "nsMessengerWinIntegration.h"
#endif
@ -330,6 +332,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgGroupView)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgOfflineManager)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgProgress)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSpamSettings)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgTagService)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsCidProtocolHandler)
#ifdef XP_WIN
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMessengerWinIntegration, Init)
@ -826,6 +829,10 @@ static const nsModuleComponentInfo gComponents[] = {
NS_SPAMSETTINGS_CONTRACTID,
nsSpamSettingsConstructor,
},
{ "Tag Service", NS_MSGTAGSERVICE_CID,
NS_MSGTAGSERVICE_CONTRACTID,
nsMsgTagServiceConstructor,
},
{ "cid protocol", NS_CIDPROTOCOL_CID,
NS_CIDPROTOCOLHANDLER_CONTRACTID,
nsCidProtocolHandlerConstructor,

View File

@ -47,8 +47,7 @@ nsMsgRecipientArray::nsMsgRecipientArray()
nsMsgRecipientArray::~nsMsgRecipientArray()
{
if (m_array)
delete m_array;
delete m_array;
}
/* the following macro actually implement addref, release and query interface for our class. */
@ -56,13 +55,13 @@ NS_IMPL_ISUPPORTS1(nsMsgRecipientArray, nsIMsgRecipientArray)
nsresult nsMsgRecipientArray::StringAt(PRInt32 idx, PRUnichar **_retval)
{
if (!_retval || !m_array)
return NS_ERROR_NULL_POINTER;
if (!_retval || !m_array)
return NS_ERROR_NULL_POINTER;
nsString aStr;
m_array->StringAt(idx, aStr);
*_retval = ToNewUnicode(aStr);
return NS_OK;
nsString aStr;
m_array->StringAt(idx, aStr);
*_retval = ToNewUnicode(aStr);
return NS_OK;
}
nsresult nsMsgRecipientArray::AppendString(const PRUnichar *aString, PRBool *_retval)

View File

@ -876,6 +876,8 @@ nsMsgSendLater::BuildHeaders()
prune_p = do_flags_p = PR_TRUE;
else if (!PL_strncasecmp(HEADER_X_MOZILLA_DRAFT_INFO, buf, end - buf))
prune_p = do_return_receipt_p = PR_TRUE;
else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf))
prune_p = PR_TRUE;
else if (!PL_strncasecmp(HEADER_X_MOZILLA_NEWSHOST, buf, end - buf))
{
prune_p = PR_TRUE;

View File

@ -290,7 +290,9 @@ interface nsIMsgDatabase : nsIDBChangeAnnouncer {
// for msg hdr hash table allocation. controllable by caller to improve folder loading preformance.
attribute unsigned long msgHdrCacheSize;
void setFolderStream(in nsIOFileStream aFileStream);
// extremely deprecated - this is going away when we get rid of nsFileSpec and friends.
// and it's [noscript] though I don't know if you can make an attribute noscript.
attribute nsIOFileStream folderStream;
/**
* The list of messages currently in the NEW state.

View File

@ -43,21 +43,23 @@ typedef unsigned short imapMessageFlagsType;
typedef long nsOfflineImapOperationType;
[scriptable, uuid(7cc7dec6-ea50-11d4-a5b7-0060b0fc04b7)]
[scriptable, uuid(2728cb2b-4716-4b5e-98a7-ce22569378e5)]
interface nsIMsgOfflineImapOperation : nsISupports
{
// type of stored imap operations
const long kFlagsChanged = 0x1;
const long kMsgMoved = 0x2;
const long kMsgCopy = 0x4;
const long kMoveResult = 0x8;
const long kAppendDraft = 0x10;
const long kAddedHeader = 0x20;
const long kDeletedMsg = 0x40;
const long kFlagsChanged = 0x1;
const long kMsgMoved = 0x2;
const long kMsgCopy = 0x4;
const long kMoveResult = 0x8;
const long kAppendDraft = 0x10;
const long kAddedHeader = 0x20;
const long kDeletedMsg = 0x40;
const long kMsgMarkedDeleted = 0x80;
const long kAppendTemplate = 0x100;
const long kDeleteAllMsgs = 0x200;
const long kAppendTemplate = 0x100;
const long kDeleteAllMsgs = 0x200;
const long kAddKeywords = 0x400;
const long kRemoveKeywords = 0x800;
attribute nsOfflineImapOperationType operation;
void clearOperation(in nsOfflineImapOperationType operation);
@ -66,6 +68,10 @@ interface nsIMsgOfflineImapOperation : nsISupports
attribute imapMessageFlagsType newFlags; // for kFlagsChanged
attribute string destinationFolderURI; // for move or copy
attribute string sourceFolderURI;
void addKeywordToAdd(in string aKeyword);
void addKeywordToRemove(in string aKeyword);
readonly attribute string keywordsToAdd;
readonly attribute string keywordsToRemove;
readonly attribute long numberOfCopies;
void addMessageCopyOperation(in string destinationBox);
string getCopyDestination(in long copyIndex);

View File

@ -57,6 +57,7 @@ public:
NS_IMETHOD ForceClosed();
NS_IMETHOD SetFolderStream(nsIOFileStream *aFileStream);
NS_IMETHOD GetFolderStream(nsIOFileStream **aFileStream);
NS_IMETHOD AddNewHdrToDB(nsIMsgDBHdr *newHdr, PRBool notify);
NS_IMETHOD SetAttributesOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
const char *propertyVal, PRInt32 flags);

View File

@ -76,6 +76,7 @@ public:
NS_IMETHOD ListAllOfflineDeletes(nsMsgKeyArray *offlineDeletes);
NS_IMETHOD SetFolderStream(nsIOFileStream *aFileStream);
NS_IMETHOD GetFolderStream(nsIOFileStream **aFileStream);
friend class nsMsgOfflineOpEnumerator;
protected:

View File

@ -188,7 +188,7 @@ protected:
static void AddToCache(nsMsgDatabase* pMessageDB)
{
#ifdef DEBUG_David_Bienvenu
NS_ASSERTION(GetNumInCache() < 28, "28 or more open db's");
NS_ASSERTION(GetNumInCache() < 40, "40 or more open db's");
#endif
GetDBCache()->AppendElement(pMessageDB);}
static void RemoveFromCache(nsMsgDatabase* pMessageDB);

View File

@ -117,6 +117,11 @@ NS_IMETHODIMP nsImapMailDatabase::ForceClosed()
return nsMailDatabase::ForceClosed();
}
NS_IMETHODIMP nsImapMailDatabase::GetFolderStream(nsIOFileStream **aFileStream)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsImapMailDatabase::SetFolderStream(nsIOFileStream *aFileStream)
{
NS_ASSERTION(0, "Trying to set folderStream, not implemented");

View File

@ -79,6 +79,19 @@ NS_IMETHODIMP nsMailDatabase::SetFolderStream(nsIOFileStream *aFileStream)
return NS_OK;
}
NS_IMETHODIMP nsMailDatabase::GetFolderStream(nsIOFileStream **aFileStream)
{
NS_ENSURE_ARG_POINTER(aFileStream);
if (!m_folderStream)
{
m_folderStream = new nsIOFileStream(nsFileSpec(*m_folderSpec));
m_ownFolderStream = PR_TRUE;
}
// N.B. - not a ref-counted interface pointer
*aFileStream = m_folderStream;
return NS_OK;
}
static PRBool gGotGlobalPrefs = PR_FALSE;
static PRInt32 gTimeStampLeeway;

View File

@ -4756,6 +4756,11 @@ NS_IMETHODIMP nsMsgDatabase::ResetHdrCacheSize(PRUint32 aSize)
return NS_OK;
}
NS_IMETHODIMP nsMsgDatabase::GetFolderStream(nsIOFileStream **aFileStream)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsMsgDatabase::SetFolderStream(nsIOFileStream *aFileStream)
{
NS_ASSERTION(0, "Trying to set the folderStream, not implemented");

View File

@ -43,6 +43,7 @@
#include "msgCore.h"
#include "nsMsgOfflineImapOperation.h"
#include "nsReadableUtils.h"
#include "nsMsgUtils.h"
PRLogModuleInfo *IMAPOffline;
@ -61,6 +62,8 @@ NS_IMPL_ISUPPORTS1(nsMsgOfflineImapOperation, nsIMsgOfflineImapOperation)
#define PROP_NUM_COPY_DESTS "numCopyDests"
#define PROP_COPY_DESTS "copyDests" // how to delimit these? Or should we do the "dest1","dest2" etc trick? But then we'd need to shuffle
// them around since we delete off the front first.
#define PROP_KEYWORD_ADD "addedKeywords"
#define PROP_KEYWORD_REMOVE "removedKeywords"
nsMsgOfflineImapOperation::nsMsgOfflineImapOperation(nsMsgDatabase *db, nsIMdbRow *row)
{
@ -209,6 +212,77 @@ NS_IMETHODIMP nsMsgOfflineImapOperation::SetSourceFolderURI(const char * aSource
return m_mdb->SetProperty(m_mdbRow, PROP_SRC_FOLDER_URI, aSourceFolderURI);
}
/* attribute string keyword; */
NS_IMETHODIMP nsMsgOfflineImapOperation::GetKeywordsToAdd(char * *aKeywords)
{
NS_ENSURE_ARG(aKeywords);
nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_KEYWORD_ADD, getter_Copies(m_keywordsToAdd));
*aKeywords = nsCRT::strdup(m_keywordsToAdd);
return rv;
}
NS_IMETHODIMP nsMsgOfflineImapOperation::AddKeywordToAdd(const char * aKeyword)
{
return AddKeyword(aKeyword, m_keywordsToAdd, PROP_KEYWORD_ADD, m_keywordsToRemove, PROP_KEYWORD_REMOVE);
nsACString::const_iterator start, end;
if (!MsgFindKeyword(nsDependentCString(aKeyword), m_keywordsToAdd, start, end))
{
if (!m_keywordsToAdd.IsEmpty())
m_keywordsToAdd.Append(' ');
m_keywordsToAdd.Append(aKeyword);
}
// if the keyword we're adding was in the list of keywords to remove,
// cut it from that list.
nsACString::const_iterator removeStart, removeEnd;
if (MsgFindKeyword(nsDependentCString(aKeyword), m_keywordsToRemove, removeStart, removeEnd))
{
nsACString::const_iterator saveStart;
m_keywordsToRemove.BeginReading(saveStart);
m_keywordsToRemove.Cut(Distance(saveStart, removeStart), Distance(removeStart, removeEnd));
m_mdb->SetProperty(m_mdbRow, PROP_KEYWORD_REMOVE, m_keywordsToRemove.get());
}
SetOperation(kAddKeywords);
return m_mdb->SetProperty(m_mdbRow, PROP_KEYWORD_ADD, m_keywordsToAdd.get());
}
NS_IMETHODIMP nsMsgOfflineImapOperation::GetKeywordsToRemove(char * *aKeywords)
{
NS_ENSURE_ARG(aKeywords);
nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_KEYWORD_REMOVE, getter_Copies(m_keywordsToRemove));
*aKeywords = nsCRT::strdup(m_keywordsToRemove);
return rv;
}
nsresult nsMsgOfflineImapOperation::AddKeyword(const char *aKeyword, nsCString &addList, const char *addProp,
nsCString &removeList, const char *removeProp)
{
nsACString::const_iterator start, end;
if (!MsgFindKeyword(nsDependentCString(aKeyword), addList, start, end))
{
if (!addList.IsEmpty())
addList.Append(' ');
addList.Append(aKeyword);
}
// if the keyword we're removing was in the list of keywords to add,
// cut it from that list.
nsACString::const_iterator addStart, addEnd;
if (MsgFindKeyword(nsDependentCString(aKeyword), removeList, addStart, addEnd))
{
nsACString::const_iterator saveStart;
removeList.BeginReading(saveStart);
removeList.Cut(Distance(saveStart, addStart), Distance(addStart, addEnd));
m_mdb->SetProperty(m_mdbRow, removeProp, removeList.get());
}
SetOperation(kRemoveKeywords);
return m_mdb->SetProperty(m_mdbRow, addProp, addList.get());
}
NS_IMETHODIMP nsMsgOfflineImapOperation::AddKeywordToRemove(const char * aKeyword)
{
return AddKeyword(aKeyword, m_keywordsToRemove, PROP_KEYWORD_REMOVE, m_keywordsToAdd, PROP_KEYWORD_ADD);
}
NS_IMETHODIMP nsMsgOfflineImapOperation::AddMessageCopyOperation(const char *destinationBox)
{
SetOperation(kMsgCopy);
@ -325,5 +399,13 @@ void nsMsgOfflineImapOperation::Log(PRLogModuleInfo *logFile)
{
PR_LOG(IMAPOffline, PR_LOG_ALWAYS, ("msg id %x append draft", m_messageKey));
}
if (m_operation & nsIMsgOfflineImapOperation::kAddKeywords)
{
PR_LOG(IMAPOffline, PR_LOG_ALWAYS, ("msg id %x add keyword:%s", m_messageKey, m_keywordsToAdd.get()));
}
if (m_operation & nsIMsgOfflineImapOperation::kRemoveKeywords)
{
PR_LOG(IMAPOffline, PR_LOG_ALWAYS, ("msg id %x remove keyword:%s", m_messageKey, m_keywordsToRemove.get()));
}
}

View File

@ -45,29 +45,35 @@
class nsMsgOfflineImapOperation : public nsIMsgOfflineImapOperation
{
public:
/** Instance Methods **/
nsMsgOfflineImapOperation(nsMsgDatabase *db, nsIMdbRow *row);
virtual ~nsMsgOfflineImapOperation();
/** Instance Methods **/
nsMsgOfflineImapOperation(nsMsgDatabase *db, nsIMdbRow *row);
virtual ~nsMsgOfflineImapOperation();
NS_DECL_ISUPPORTS
NS_DECL_NSIMSGOFFLINEIMAPOPERATION
nsIMdbRow *GetMDBRow() {return m_mdbRow;}
nsIMdbRow *GetMDBRow() {return m_mdbRow;}
nsresult GetCopiesFromDB();
nsresult SetCopiesToDB();
void Log(PRLogModuleInfo *logFile);
protected:
nsresult AddKeyword(const char *aKeyword, nsCString &addList, const char *addProp,
nsCString &removeList, const char *removeProp);
nsOfflineImapOperationType m_operation;
nsMsgKey m_messageKey;
nsMsgKey m_sourceMessageKey;
PRUint32 m_operationFlags; // what to do on sync
imapMessageFlagsType m_newFlags; // used for kFlagsChanged
nsMsgKey m_messageKey;
nsMsgKey m_sourceMessageKey;
PRUint32 m_operationFlags; // what to do on sync
imapMessageFlagsType m_newFlags; // used for kFlagsChanged
// these are URI's, and are escaped. Thus, we can use a delimter like ' '
// because the real spaces should be escaped.
nsXPIDLCString m_sourceFolder;
nsXPIDLCString m_moveDestination;
nsCStringArray m_copyDestinations;
nsXPIDLCString m_sourceFolder;
nsXPIDLCString m_moveDestination;
nsCStringArray m_copyDestinations;
nsXPIDLCString m_keywordsToAdd;
nsXPIDLCString m_keywordsToRemove;
// nsMsgOfflineImapOperation will have to know what db and row they belong to, since they are really
// just a wrapper around the offline operation row in the mdb.

View File

@ -3462,6 +3462,16 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
keysToFlag.GetSize(), nsnull);
}
break;
case nsMsgFilterAction::AddTag:
{
nsXPIDLCString keyword;
filterAction->GetStrValue(getter_Copies(keyword));
nsCOMPtr<nsISupportsArray> messageArray;
NS_NewISupportsArray(getter_AddRefs(messageArray));
messageArray->AppendElement(msgHdr);
AddKeywordToMessages(messageArray, keyword.get());
break;
}
case nsMsgFilterAction::JunkScore:
{
nsCAutoString junkScoreStr;
@ -7063,7 +7073,7 @@ nsImapFolderCopyState::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
PRUint32 childCount;
m_srcFolder->Count(&childCount);
for (PRInt32 childIndex = 0; childIndex < childCount; childIndex++)
for (PRUint32 childIndex = 0; childIndex < childCount; childIndex++)
{
nsCOMPtr <nsISupports> child = do_QueryElementAt(m_srcFolder, childIndex, &rv);
if (NS_SUCCEEDED(rv))
@ -7931,7 +7941,7 @@ NS_IMETHODIMP nsImapMailFolder::RenameClient(nsIMsgWindow *msgWindow, nsIMsgFold
nsCOMPtr<nsIMsgFolder> msgParent;
msgFolder->GetParentMsgFolder(getter_AddRefs(msgParent));
msgFolder->SetParent(nsnull);
msgParent->PropagateDelete(msgFolder,PR_FALSE, nsnull);
msgParent->PropagateDelete(msgFolder, PR_TRUE, nsnull);
// Reset online status now that the folder is renamed.
nsCOMPtr <nsIMsgImapMailFolder> oldImapFolder = do_QueryInterface(msgFolder);
@ -8149,6 +8159,28 @@ nsImapMailFolder::StoreCustomKeywords(nsIMsgWindow *aMsgWindow, const char *aFla
const char *aFlagsToSubtract, nsMsgKey *aKeysToStore, PRUint32 aNumKeys, nsIURI **_retval)
{
nsresult rv;
if (WeAreOffline())
{
GetDatabase(nsnull);
if (mDatabase)
{
for (PRUint32 keyIndex = 0; keyIndex < aNumKeys; keyIndex++)
{
nsCOMPtr <nsIMsgOfflineImapOperation> op;
rv = mDatabase->GetOfflineOpForKey(aKeysToStore[keyIndex], PR_TRUE, getter_AddRefs(op));
SetFlag(MSG_FOLDER_FLAG_OFFLINEEVENTS);
if (NS_SUCCEEDED(rv) && op)
{
if (aFlagsToAdd)
op->AddKeywordToAdd(aFlagsToAdd);
if (aFlagsToSubtract)
op->AddKeywordToRemove(aFlagsToSubtract);
}
}
mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline ops
return rv;
}
}
nsCOMPtr<nsIImapService> imapService(do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString msgIds;
@ -8471,3 +8503,34 @@ NS_IMETHODIMP nsImapMailFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, PRUi
return NS_OK;
}
NS_IMETHODIMP nsImapMailFolder::AddKeywordToMessages(nsISupportsArray *aMessages, const char *aKeyword)
{
nsresult rv = nsMsgDBFolder::AddKeywordToMessages(aMessages, aKeyword);
if (NS_SUCCEEDED(rv))
{
nsCAutoString messageIds;
nsMsgKeyArray keys;
rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
NS_ENSURE_SUCCESS(rv, rv);
rv = StoreCustomKeywords(nsnull, aKeyword, nsnull, keys.GetArray(), keys.GetSize(), nsnull);
if (mDatabase)
mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
}
return rv;
}
NS_IMETHODIMP nsImapMailFolder::RemoveKeywordFromMessages(nsISupportsArray *aMessages, const char *aKeyword)
{
nsresult rv = nsMsgDBFolder::RemoveKeywordFromMessages(aMessages, aKeyword);
if (NS_SUCCEEDED(rv))
{
nsCAutoString messageIds;
nsMsgKeyArray keys;
nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
NS_ENSURE_SUCCESS(rv, rv);
rv = StoreCustomKeywords(nsnull, nsnull, aKeyword, keys.GetArray(), keys.GetSize(), nsnull);
if (mDatabase)
mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
}
return rv;
}

View File

@ -291,6 +291,9 @@ public:
PRBool aLocalOnly, nsIUrlListener *aUrlListener,
PRBool *aAsyncResults);
NS_IMETHOD AddKeywordToMessages(nsISupportsArray *aMessages, const char *aKeyword);
NS_IMETHOD RemoveKeywordFromMessages(nsISupportsArray *aMessages, const char *aKeyword);
// nsIMsgImapMailFolder methods
NS_DECL_NSIMSGIMAPMAILFOLDER

View File

@ -294,6 +294,77 @@ void nsImapOfflineSync::ProcessFlagOperation(nsIMsgOfflineImapOperation *op)
ProcessNextOperation();
}
void nsImapOfflineSync::ProcessKeywordOperation(nsIMsgOfflineImapOperation *op)
{
nsCOMPtr <nsIMsgOfflineImapOperation> currentOp = op;
nsMsgKeyArray matchingKeywordKeys;
PRUint32 currentKeyIndex = m_KeyIndex;
nsXPIDLCString keywords;
if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
currentOp->GetKeywordsToAdd(getter_Copies(keywords));
else
currentOp->GetKeywordsToRemove(getter_Copies(keywords));
PRBool keywordsMatch = PR_TRUE;
do
{ // loop for all messsages with the same keywords
if (keywordsMatch)
{
nsMsgKey curKey;
currentOp->GetMessageKey(&curKey);
matchingKeywordKeys.Add(curKey);
currentOp->ClearOperation(mCurrentPlaybackOpType);
}
currentOp = nsnull;
if (++currentKeyIndex < m_CurrentKeys.GetSize())
m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], PR_FALSE,
getter_AddRefs(currentOp));
if (currentOp)
{
nsXPIDLCString curOpKeywords;
nsOfflineImapOperationType operation;
currentOp->GetOperation(&operation);
if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
currentOp->GetKeywordsToAdd(getter_Copies(curOpKeywords));
else
currentOp->GetKeywordsToRemove(getter_Copies(curOpKeywords));
keywordsMatch = (operation & mCurrentPlaybackOpType)
&& (curOpKeywords.Equals(keywords));
}
} while (currentOp);
if (matchingKeywordKeys.GetSize() > 0)
{
PRUint32 curFolderFlags;
m_currentFolder->GetFlags(&curFolderFlags);
if (curFolderFlags & MSG_FOLDER_FLAG_IMAPBOX)
{
nsresult rv = NS_OK;
nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder);
nsCOMPtr <nsIURI> uriToStoreCustomKeywords;
if (imapFolder)
{
rv = imapFolder->StoreCustomKeywords(m_window,
(mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords) ? keywords.get() : nsnull,
(mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kRemoveKeywords) ? keywords.get() : nsnull,
matchingKeywordKeys.GetArray(),
matchingKeywordKeys.GetSize(), getter_AddRefs(uriToStoreCustomKeywords));
if (NS_SUCCEEDED(rv) && uriToStoreCustomKeywords)
{
nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(uriToStoreCustomKeywords);
if (mailnewsUrl)
mailnewsUrl->RegisterListener(this);
}
}
}
}
else
ProcessNextOperation();
}
void
nsImapOfflineSync::ProcessAppendMsgOperation(nsIMsgOfflineImapOperation *currentOp, PRInt32 opType)
{
@ -800,6 +871,20 @@ nsresult nsImapOfflineSync::ProcessNextOperation()
{
// we are done with the current type
if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kFlagsChanged)
{
mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAddKeywords;
// recurse to deal with next type of operation
m_KeyIndex = 0;
ProcessNextOperation();
}
else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
{
mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kRemoveKeywords;
// recurse to deal with next type of operation
m_KeyIndex = 0;
ProcessNextOperation();
}
else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kRemoveKeywords)
{
mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kMsgCopy;
// recurse to deal with next type of operation
@ -844,6 +929,9 @@ nsresult nsImapOfflineSync::ProcessNextOperation()
{
if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kFlagsChanged)
ProcessFlagOperation(currentOp);
else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords
||mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kRemoveKeywords)
ProcessKeywordOperation(currentOp);
else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgCopy)
ProcessCopyOperation(currentOp);
else if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgMoved)

View File

@ -73,6 +73,7 @@ protected:
void DeleteAllOfflineOpsForCurrentDB();
void ProcessFlagOperation(nsIMsgOfflineImapOperation *currentOp);
void ProcessKeywordOperation(nsIMsgOfflineImapOperation *op);
void ProcessMoveOperation(nsIMsgOfflineImapOperation *currentOp);
void ProcessCopyOperation(nsIMsgOfflineImapOperation *currentOp);
void ProcessEmptyTrash(nsIMsgOfflineImapOperation *currentOp);

View File

@ -86,7 +86,6 @@
#include "nsICopyMsgStreamListener.h"
#include "nsIFileStream.h"
#include "nsIMsgParseMailMsgState.h"
#include "nsMsgLineBuffer.h"
#include "nsMsgLocalCID.h"
#include "nsIOutputStream.h"
#include "nsIDocShell.h"
@ -1589,10 +1588,6 @@ nsImapService::SetMessageFlags(nsIEventTarget * aClientEventTarget,
imapMessageFlagsType flags,
PRBool messageIdsAreUID)
{
// create a protocol instance to handle the request.
// NOTE: once we start working with multiple connections, this step will be much more complicated...but for now
// just create a connection and process the request.
return DiddleFlags(aClientEventTarget, aImapMailFolder, aUrlListener, aURL, messageIdentifierList,
"setmsgflags", flags, messageIdsAreUID);
}

View File

@ -2507,11 +2507,13 @@ keepGoing:
void nsMsgLocalMailFolder::CopyPropertiesToMsgHdr(nsIMsgDBHdr *destHdr, nsIMsgDBHdr *srcHdr)
{
nsXPIDLCString sourceJunkScore;
srcHdr->GetStringProperty("junkscore", getter_Copies(sourceJunkScore));
destHdr->SetStringProperty("junkscore", sourceJunkScore);
srcHdr->GetStringProperty("junkscoreorigin", getter_Copies(sourceJunkScore));
destHdr->SetStringProperty("junkscoreorigin", sourceJunkScore);
nsXPIDLCString sourceString;
srcHdr->GetStringProperty("junkscore", getter_Copies(sourceString));
destHdr->SetStringProperty("junkscore", sourceString);
srcHdr->GetStringProperty("junkscoreorigin", getter_Copies(sourceString));
destHdr->SetStringProperty("junkscoreorigin", sourceString);
srcHdr->GetStringProperty("keywords", getter_Copies(sourceString));
destHdr->SetStringProperty("junkscoreorigin", sourceString);
nsMsgLabelValue label = 0;
srcHdr->GetLabel(&label);
@ -3873,3 +3875,141 @@ NS_IMETHODIMP nsMsgLocalMailFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch,
return rv;
}
NS_IMETHODIMP nsMsgLocalMailFolder::AddKeywordToMessages(nsISupportsArray *aMessages, const char *aKeyword)
{
return ChangeKeywordForMessages(aMessages, aKeyword, PR_TRUE /* add */);
}
nsresult nsMsgLocalMailFolder::ChangeKeywordForMessages(nsISupportsArray *aMessages, const char *aKeyword, PRBool add)
{
nsresult rv = (add) ? nsMsgDBFolder::AddKeywordToMessages(aMessages, aKeyword)
: nsMsgDBFolder::RemoveKeywordFromMessages(aMessages, aKeyword);
if (NS_SUCCEEDED(rv))
{
rv = GetDatabase(nsnull);
NS_ENSURE_SUCCESS(rv, rv);
// this will fail if the folder is locked.
rv = mDatabase->StartBatch();
NS_ENSURE_SUCCESS(rv, rv);
nsIOFileStream *fileStream;
rv = mDatabase->GetFolderStream(&fileStream);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 count;
NS_ENSURE_ARG(aMessages);
nsresult rv = aMessages->Count(&count);
NS_ENSURE_SUCCESS(rv, rv);
nsXPIDLCString keywords;
nsCAutoString keywordToWrite(" ");
keywordToWrite.Append(aKeyword);
// for each message, we seek to the beginning of the x-mozilla-status header, and
// start reading lines, looking for x-mozilla-keys: headers; If we're adding
// the keyword and we find
// a header with the desired keyword already in it, we don't need to
// do anything. Likewise, if removing keyword and we don't find it,
// we don't need to do anything. Otherwise, if adding, we need to
// see if there's an x-mozilla-keys
// header with room for the new keyword. If so, we replace the
// corresponding number of spaces with the keyword. If no room,
// we can't do anything until the folder is compacted and another
// x-mozilla-keys header is added. In that case, we set a property
// on the header, which the compaction code will check.
// don't return out of the for loop - otherwise, we won't call EndBatch();
for(PRUint32 i = 0; i < count; i++) // for each message
{
char lineBuff[500];
nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 messageOffset;
PRUint32 len = 0;
nsCAutoString header;
nsCAutoString keywords;
message->GetMessageOffset(&messageOffset);
PRBool done = PR_FALSE;
PRUint32 statusOffset = 0;
(void)message->GetStatusOffset(&statusOffset);
PRUint32 desiredOffset = messageOffset + statusOffset;
fileStream->seek(PR_SEEK_SET, desiredOffset);
PRBool inKeywordHeader = PR_FALSE;
PRBool foundKeyword = PR_FALSE;
PRUint32 offsetToAddKeyword = 0;
message->GetMessageSize(&len);
// loop through
while (!done)
{
lineBuff[0] = '\0';
PRInt32 lineStartPos = fileStream->tell();
// readLine won't return line termination chars.
if (fileStream->readline(lineBuff, sizeof(lineBuff)))
{
if (EMPTY_MESSAGE_LINE(lineBuff))
break; // passed headers; no x-mozilla-keywords header; give up.
nsCString keywordHeaders;
if (!strncmp(lineBuff, HEADER_X_MOZILLA_KEYWORDS, sizeof(HEADER_X_MOZILLA_KEYWORDS) - 1))
{
inKeywordHeader = PR_TRUE;
keywordHeaders = lineBuff;
}
else if (inKeywordHeader && (lineBuff[0] == ' ' || lineBuff[0] == '\t'))
keywordHeaders = lineBuff;
else if (inKeywordHeader)
break;
else
continue;
PRInt32 keywordHdrLength = keywordHeaders.Length();
nsACString::const_iterator start, end;
nsACString::const_iterator keywordHdrStart;
keywordHeaders.BeginReading(keywordHdrStart);
// check if we have the keyword
if (MsgFindKeyword(nsDependentCString(aKeyword), keywordHeaders, start, end))
{
foundKeyword = PR_TRUE;
if (!add) // if we're removing, remove it, and break;
{
PRInt32 keywordStartOffset = Distance(keywordHdrStart, start);
keywordHeaders.Cut(keywordStartOffset, Distance(start, end));
for (PRInt32 i = Distance(start, end); i > 0; i--)
keywordHeaders.Append(' ');
fileStream->seek(PR_SEEK_SET, lineStartPos);
fileStream->write(keywordHeaders.get(), keywordHeaders.Length());
}
offsetToAddKeyword = 0;
// if adding and we already have the keyword, done
done = PR_TRUE;
break;
}
// argh, we need to check all the lines to see if we already have the
// keyword, but if we don't find it, we want to remember the line and
// position where we have room to add the keyword.
if (add)
{
nsCAutoString curKeywordHdr(lineBuff);
// strip off line ending spaces.
curKeywordHdr.Trim(" ", PR_FALSE, PR_TRUE);
if (!offsetToAddKeyword && curKeywordHdr.Length() + keywordToWrite.Length() < keywordHdrLength)
offsetToAddKeyword = lineStartPos + curKeywordHdr.Length();
}
}
}
if (add && !foundKeyword)
{
if (!offsetToAddKeyword)
message->SetUint32Property("growKeywords", 1);
else
{
fileStream->seek(PR_SEEK_SET, offsetToAddKeyword);
fileStream->write(keywordToWrite.get(), keywordToWrite.Length());
}
}
}
mDatabase->EndBatch();
}
return rv;
}
NS_IMETHODIMP nsMsgLocalMailFolder::RemoveKeywordFromMessages(nsISupportsArray *aMessages, const char *aKeyword)
{
return ChangeKeywordForMessages(aMessages, aKeyword, PR_FALSE /* remove */);
}

View File

@ -123,9 +123,6 @@ public:
NS_DECL_NSIMSGLOCALMAILFOLDER
NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
NS_DECL_ISUPPORTS_INHERITED
#if 0
static nsresult GetRoot(nsIMsgFolder* *result);
#endif
// nsIRDFResource methods:
NS_IMETHOD Init(const char *aURI);
@ -199,7 +196,8 @@ public:
NS_IMETHOD FetchMsgPreviewText(nsMsgKey *aKeysToFetch, PRUint32 aNumKeys,
PRBool aLocalOnly, nsIUrlListener *aUrlListener,
PRBool *aAsyncResults);
NS_IMETHOD AddKeywordToMessages(nsISupportsArray *aMessages, const char *aKeyword);
NS_IMETHOD RemoveKeywordFromMessages(nsISupportsArray *aMessages, const char *aKeyword);
protected:
nsresult CopyFolderAcrossServer(nsIMsgFolder *srcFolder, nsIMsgWindow *msgWindow,nsIMsgCopyServiceListener* listener);
@ -233,6 +231,7 @@ protected:
virtual nsresult CreateBaseMessageURI(const char *aURI);
virtual nsresult SpamFilterClassifyMessage(const char *aURI, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin);
virtual nsresult SpamFilterClassifyMessages(const char **aURIArray, PRUint32 aURICount, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin);
nsresult ChangeKeywordForMessages(nsISupportsArray *aMessages, const char *aKeyword, PRBool add);
protected:
nsLocalMailCopyState *mCopyState; //We only allow one of these at a time
const char *mType;

View File

@ -513,6 +513,7 @@ NS_IMETHODIMP nsParseMailMessageState::Clear()
m_envelope_from.length = 0;
m_envelope_date.length = 0;
m_priority.length = 0;
m_keywords.length = 0;
m_mdn_dnt.length = 0;
m_return_path.length = 0;
m_account_key.length = 0;
@ -945,6 +946,9 @@ int nsParseMailMessageState::ParseHeaders ()
else if (!nsCRT::strncasecmp("X-Priority", buf, end - buf)
|| !nsCRT::strncasecmp("Priority", buf, end - buf))
header = &m_priority;
else if (!nsCRT::strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf)
&& !m_keywords.length)
header = &m_keywords;
break;
}
@ -1158,6 +1162,7 @@ int nsParseMailMessageState::FinalizeHeaders()
struct message_header *mozstatus;
struct message_header *mozstatus2;
struct message_header *priority;
struct message_header *keywords;
struct message_header *account_key;
struct message_header *ccList;
struct message_header *mdn_dnt;
@ -1193,11 +1198,12 @@ int nsParseMailMessageState::FinalizeHeaders()
references = (m_references.length ? &m_references : 0);
statush = (m_status.length ? &m_status : 0);
mozstatus = (m_mozstatus.length ? &m_mozstatus : 0);
mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0);
mozstatus2 = (m_mozstatus2.length ? &m_mozstatus2 : 0);
date = (m_date.length ? &m_date :
m_envelope_date.length ? &m_envelope_date :
0);
priority = (m_priority.length ? &m_priority : 0);
keywords = (m_keywords.length ? &m_keywords : 0);
mdn_dnt = (m_mdn_dnt.length ? &m_mdn_dnt : 0);
inReplyTo = (m_in_reply_to.length ? &m_in_reply_to : 0);
replyTo = (m_replyTo.length ? &m_replyTo : 0);
@ -1422,6 +1428,8 @@ int nsParseMailMessageState::FinalizeHeaders()
m_newMsgHdr->SetPriorityString(priority->value);
else if (priorityFlags == nsMsgPriority::notSet)
m_newMsgHdr->SetPriority(nsMsgPriority::none);
if (keywords)
m_newMsgHdr->SetStringProperty("keywords", keywords->value);
if (content_type)
{
char *substring = PL_strstr(content_type->value, "charset");
@ -1887,6 +1895,16 @@ NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWi
filterAction->GetPriority(&filterPriority);
msgHdr->SetPriority(filterPriority);
break;
case nsMsgFilterAction::AddTag:
{
nsXPIDLCString keyword;
filterAction->GetStrValue(getter_Copies(keyword));
nsCOMPtr<nsISupportsArray> messageArray;
NS_NewISupportsArray(getter_AddRefs(messageArray));
messageArray->AppendElement(msgHdr);
m_downloadFolder->AddKeywordToMessages(messageArray, keyword.get());
break;
}
case nsMsgFilterAction::Label:
nsMsgLabelValue filterLabel;
filterAction->GetLabel(&filterLabel);
@ -1966,13 +1984,13 @@ NS_IMETHODIMP nsParseNewMailState::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWi
nsCOMPtr<nsISupports> iSupports = do_QueryInterface(msgHdr);
messages->AppendElement(iSupports);
localFolder->MarkMsgsOnPop3Server(messages, POP3_FETCH_BODY);
// Don't add this header to the DB, we're going to replace it
// with the full message.
// Don't add this header to the DB, we're going to replace it
// with the full message.
m_msgMovedByFilter = PR_TRUE;
msgIsNew = PR_FALSE;
// Don't do anything else in this filter, wait until we
// have the full message.
*applyMore = PR_FALSE;
// Don't do anything else in this filter, wait until we
// have the full message.
*applyMore = PR_FALSE;
}
}
break;

View File

@ -141,6 +141,7 @@ public:
struct message_header m_envelope_date;
struct message_header m_priority;
struct message_header m_account_key;
struct message_header m_keywords;
// Mdn support
struct message_header m_mdn_original_recipient;
struct message_header m_return_path;

View File

@ -593,6 +593,8 @@ nsPop3Sink::IncorporateBegin(const char* uidlString,
if (NS_FAILED(rv)) return rv;
rv = WriteLineToMailbox("X-Mozilla-Status2: 00000000" MSG_LINEBREAK);
if (NS_FAILED(rv)) return rv;
// leave space for 60 bytes worth of keys/tags
rv = WriteLineToMailbox(X_MOZILLA_KEYWORDS);
PR_smprintf_free(statusLine);
return NS_OK;
}

View File

@ -114,5 +114,5 @@
#define HEADER_X_MOZILLA_PART_URL "X-Mozilla-PartURL"
#define HEADER_X_MOZILLA_IDENTITY_KEY "X-Identity-Key"
#define HEADER_X_MOZILLA_ACCOUNT_KEY "X-Account-Key"
#define HEADER_X_MOZILLA_KEYWORDS "X-Mozilla-Keys"
#endif /* nsMailHeaders_h_ */