AGMS Spam Filter.
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@9669 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
SubDir OBOS_TOP src add-ons mail_daemon inbound_filters spam_filter ;
|
||||
|
||||
UsePrivateHeaders mail ;
|
||||
SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ;
|
||||
|
||||
Addon Spam\ Filter : mail_daemon inbound_filters :
|
||||
SpamFilterConfig.cpp
|
||||
SpamFilter.cpp ;
|
||||
|
||||
LinkSharedOSLibs Spam\ Filter :
|
||||
be libmail.so ;
|
||||
|
||||
Package haiku-maildaemon-cvs :
|
||||
Spam\ Filter :
|
||||
boot home config add-ons mail_daemon inbound_filters ;
|
||||
@@ -0,0 +1,479 @@
|
||||
/******************************************************************************
|
||||
* AGMSBayesianSpamFilter - Uses Bayesian statistics to evaluate the spaminess
|
||||
* of a message. The evaluation is done by a separate server, this add-on just
|
||||
* gets the text and uses scripting commands to get an evaluation from the
|
||||
* server. If the server isn't running, it will be found and started up. Once
|
||||
* the evaluation has been received, it is added to the message as an attribute
|
||||
* and optionally as an addition to the subject. Some other add-on later in
|
||||
* the pipeline will use the attribute to delete the message or move it to some
|
||||
* other folder.
|
||||
*
|
||||
* Public Domain 2002, by Alexander G. M. Smith, no warranty.
|
||||
*
|
||||
* $Log: SpamFilter.cpp,v $
|
||||
* Revision 1.1 2004/10/30 22:23:26 brunoga
|
||||
* AGMS Spam Filter.
|
||||
*
|
||||
* Revision 1.19 2004/09/20 15:57:30 nwhitehorn
|
||||
* Mostly updated the tree to Be/Haiku style identifier naming conventions. I have a few more things to work out, mostly in mail_util.h, and then I'm proceeding to jamify the build system. Then we go into Haiku CVS.
|
||||
*
|
||||
* Revision 1.18 2003/09/20 12:39:27 agmsmith
|
||||
* Memory leak delete needs [] bug.
|
||||
*
|
||||
* Revision 1.17 2003/07/08 21:12:47 agmsmith
|
||||
* Changed other spam filter defaults to values I find useful.
|
||||
*
|
||||
* Revision 1.16 2003/07/08 20:56:40 agmsmith
|
||||
* Turn on auto-training for the spam filter by default.
|
||||
*
|
||||
* Revision 1.15 2003/07/06 13:30:33 agmsmith
|
||||
* Make sure that the spam filter doesn't auto-train the message twice
|
||||
* when it gets a partially downloaded e-mail (will just train on the
|
||||
* partial one, ignore the complete message when it gets downloaded).
|
||||
*
|
||||
* Revision 1.14 2003/05/27 17:12:59 nwhitehorn
|
||||
* Massive refactoring of the Protocol/ChainRunner/Filter system. You can probably
|
||||
* examine its scope by examining the number of files changed. Regardless, this is
|
||||
* preparation for lots of new features, and REAL WORKING IMAP. Yes, you heard me.
|
||||
* Enjoy, and prepare for bugs (although I've fixed all the ones I've found, I susp
|
||||
* ect there are some memory leaks in ChainRunner).
|
||||
*
|
||||
* Revision 1.13 2003/02/08 21:54:17 agmsmith
|
||||
* Updated the AGMSBayesianSpamServer documentation to match the current
|
||||
* version. Also removed the Beep options from the spam filter, now they
|
||||
* are turned on or off in the system sound preferences.
|
||||
*
|
||||
* Revision 1.12 2002/12/18 02:27:45 agmsmith
|
||||
* Added uncertain classification as suggested by BiPolar.
|
||||
*
|
||||
* Revision 1.11 2002/12/16 16:03:20 agmsmith
|
||||
* Changed spam cutoff to 0.95 to work with default Chi-Squared scoring.
|
||||
*
|
||||
* Revision 1.10 2002/12/13 22:04:42 agmsmith
|
||||
* Changed default to turn on the Spam marker in the subject.
|
||||
*
|
||||
* Revision 1.9 2002/12/13 20:27:44 agmsmith
|
||||
* Added auto-training mode to the filter. It evaluates a message for
|
||||
* spaminess then recursively adds it to the database. This can lead
|
||||
* to weird results unless the user corrects the bad classifications.
|
||||
*
|
||||
* Revision 1.8 2002/11/28 20:20:57 agmsmith
|
||||
* Now checks if the spam database is running in headers only mode, and
|
||||
* then only downloads headers if that is the case.
|
||||
*
|
||||
* Revision 1.7 2002/11/10 19:36:26 agmsmith
|
||||
* Retry launching server a few times, but not too many.
|
||||
*
|
||||
* Revision 1.6 2002/11/03 02:21:02 agmsmith
|
||||
* Never mind, just use the SourceForge version numbers. Ugh.
|
||||
*
|
||||
* Revision 1.8 2002/10/21 16:12:09 agmsmith
|
||||
* Added option for spam if no words found, use new method of saving
|
||||
* the attribute which avoids hacking the rest of the mail system.
|
||||
*
|
||||
* Revision 1.7 2002/10/11 20:01:28 agmsmith
|
||||
* Added sound effects (system beep) for genuine and spam, plus config option
|
||||
* for it.
|
||||
*
|
||||
* Revision 1.6 2002/10/01 00:45:34 agmsmith
|
||||
* Changed default spam ratio to 0.56 from 0.9, for use with
|
||||
* the Gary Robinson method in AGMSBayesianSpamServer 1.49.
|
||||
*
|
||||
* Revision 1.5 2002/09/25 13:23:21 agmsmith
|
||||
* Don't leave the data stream at the initial position, try leaving it
|
||||
* at the end. Was having mail progress bar problems.
|
||||
*
|
||||
* Revision 1.4 2002/09/23 19:14:13 agmsmith
|
||||
* Added an option to have the server quit when done.
|
||||
*
|
||||
* Revision 1.3 2002/09/23 03:33:34 agmsmith
|
||||
* First working version, with cutoff ratio and subject modification,
|
||||
* and an attribute added if a patch is made to the Folder filter.
|
||||
*
|
||||
* Revision 1.2 2002/09/21 20:57:22 agmsmith
|
||||
* Fixed bugs so now it compiles.
|
||||
*
|
||||
* Revision 1.1 2002/09/21 20:47:15 agmsmith
|
||||
* Initial revision
|
||||
*/
|
||||
|
||||
#include <Beep.h>
|
||||
#include <fs_attr.h>
|
||||
#include <Messenger.h>
|
||||
#include <Node.h>
|
||||
#include <Path.h>
|
||||
#include <Roster.h>
|
||||
#include <String.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "SpamFilter.h"
|
||||
|
||||
// The names match the ones set up by AGMSBayesianSpamServer for sound effects.
|
||||
static const char *kAGMSBayesBeepGenuineName = "AGMSBayes-Genuine";
|
||||
static const char *kAGMSBayesBeepSpamName = "AGMSBayes-Spam";
|
||||
static const char *kAGMSBayesBeepUncertainName = "AGMSBayes-Uncertain";
|
||||
|
||||
static const char *kServerSignature =
|
||||
"application/x-vnd.agmsmith.AGMSBayesianSpamServer";
|
||||
|
||||
|
||||
AGMSBayesianSpamFilter::AGMSBayesianSpamFilter (BMessage *settings)
|
||||
: BMailFilter (settings),
|
||||
fAddSpamToSubject (false),
|
||||
fAutoTraining (true),
|
||||
fGenuineCutoffRatio (0.01f),
|
||||
fHeaderOnly (false),
|
||||
fLaunchAttemptCount (0),
|
||||
fNoWordsMeansSpam (true),
|
||||
fQuitServerWhenFinished (false),
|
||||
fSpamCutoffRatio (0.99f)
|
||||
{
|
||||
bool tempBool;
|
||||
float tempFloat;
|
||||
BMessenger tempMessenger;
|
||||
|
||||
if (settings != NULL) {
|
||||
if (settings->FindBool ("AddMarkerToSubject", &tempBool) == B_OK)
|
||||
fAddSpamToSubject = tempBool;
|
||||
if (settings->FindBool ("AutoTraining", &tempBool) == B_OK)
|
||||
fAutoTraining = tempBool;
|
||||
if (settings->FindFloat ("GenuineCutoffRatio", &tempFloat) == B_OK)
|
||||
fGenuineCutoffRatio = tempFloat;
|
||||
if (settings->FindBool ("NoWordsMeansSpam", &tempBool) == B_OK)
|
||||
fNoWordsMeansSpam = tempBool;
|
||||
if (settings->FindBool ("QuitServerWhenFinished", &tempBool) == B_OK)
|
||||
fQuitServerWhenFinished = tempBool;
|
||||
if (settings->FindFloat ("SpamCutoffRatio", &tempFloat) == B_OK)
|
||||
fSpamCutoffRatio = tempFloat;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AGMSBayesianSpamFilter::~AGMSBayesianSpamFilter ()
|
||||
{
|
||||
BMessage quitMessage (B_QUIT_REQUESTED);
|
||||
|
||||
if (fQuitServerWhenFinished && fMessengerToServer.IsValid ())
|
||||
fMessengerToServer.SendMessage (&quitMessage);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
AGMSBayesianSpamFilter::InitCheck (BString* out_message)
|
||||
{
|
||||
if (out_message != NULL)
|
||||
out_message->SetTo (
|
||||
"AGMSBayesianSpamFilter::InitCheck is never called!");
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
AGMSBayesianSpamFilter::ProcessMailMessage (
|
||||
BPositionIO** io_message,
|
||||
BEntry* io_entry,
|
||||
BMessage* io_headers,
|
||||
BPath* io_folder,
|
||||
const char* io_uid)
|
||||
{
|
||||
ssize_t amountRead;
|
||||
attr_info attributeInfo;
|
||||
const char *classificationString;
|
||||
off_t dataSize;
|
||||
BPositionIO *dataStreamPntr = *io_message;
|
||||
status_t errorCode = B_OK;
|
||||
int32 headerLength;
|
||||
BString headerString;
|
||||
BString newSubjectString;
|
||||
BNode nodeForOutputFile;
|
||||
bool nodeForOutputFileInitialised = false;
|
||||
const char *oldSubjectStringPntr;
|
||||
char percentageString [30];
|
||||
BMessage replyMessage;
|
||||
BMessage scriptingMessage;
|
||||
team_id serverTeam;
|
||||
float spamRatio;
|
||||
char *stringBuffer = NULL;
|
||||
char tempChar;
|
||||
status_t tempErrorCode;
|
||||
const char *tokenizeModeStringPntr;
|
||||
|
||||
// Set up a BNode to the final output file so that we can write custom
|
||||
// attributes to it. Non-custom attributes are stored separately in
|
||||
// io_headers.
|
||||
|
||||
if (io_entry != NULL && B_OK == nodeForOutputFile.SetTo (io_entry))
|
||||
nodeForOutputFileInitialised = true;
|
||||
|
||||
// Get a connection to the spam database server. Launch if needed, should
|
||||
// only need it once, unless another e-mail thread shuts down the server
|
||||
// inbetween messages. This code used to be in InitCheck, but apparently
|
||||
// that isn't called.
|
||||
|
||||
if (fLaunchAttemptCount == 0 || !fMessengerToServer.IsValid ()) {
|
||||
if (fLaunchAttemptCount > 3)
|
||||
goto ErrorExit; // Don't try to start the server too many times.
|
||||
fLaunchAttemptCount++;
|
||||
|
||||
// Make sure the server is running.
|
||||
if (!be_roster->IsRunning (kServerSignature)) {
|
||||
errorCode = be_roster->Launch (kServerSignature);
|
||||
if (errorCode != B_OK)
|
||||
goto ErrorExit;
|
||||
}
|
||||
|
||||
// Set up the messenger to the database server.
|
||||
serverTeam = be_roster->TeamFor (kServerSignature);
|
||||
if (serverTeam < 0)
|
||||
goto ErrorExit;
|
||||
fMessengerToServer =
|
||||
BMessenger (kServerSignature, serverTeam, &errorCode);
|
||||
if (!fMessengerToServer.IsValid ())
|
||||
goto ErrorExit;
|
||||
|
||||
// Check if the server is running in headers only mode. If so, we only
|
||||
// need to download the header rather than the entire message.
|
||||
scriptingMessage.MakeEmpty ();
|
||||
scriptingMessage.what = B_GET_PROPERTY;
|
||||
scriptingMessage.AddSpecifier ("TokenizeMode");
|
||||
replyMessage.MakeEmpty ();
|
||||
if ((errorCode = fMessengerToServer.SendMessage (&scriptingMessage,
|
||||
&replyMessage)) != B_OK)
|
||||
goto ErrorExit;
|
||||
if ((errorCode = replyMessage.FindInt32 ("error", &tempErrorCode))
|
||||
!= B_OK)
|
||||
goto ErrorExit;
|
||||
if ((errorCode = tempErrorCode) != B_OK)
|
||||
goto ErrorExit;
|
||||
if ((errorCode = replyMessage.FindString ("result",
|
||||
&tokenizeModeStringPntr)) != B_OK)
|
||||
goto ErrorExit;
|
||||
fHeaderOnly = (tokenizeModeStringPntr != NULL
|
||||
&& strcmp (tokenizeModeStringPntr, "JustHeader") == 0);
|
||||
}
|
||||
|
||||
// See if the message has already been classified. Happens for messages
|
||||
// which are partially downloaded when you have auto-training on. Could
|
||||
// untrain the partial part before training on the complete message, but we
|
||||
// don't know how big it was, so instead just ignore the message.
|
||||
|
||||
if (nodeForOutputFileInitialised) {
|
||||
if (nodeForOutputFile.GetAttrInfo ("MAIL:classification",
|
||||
&attributeInfo) == B_OK)
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
// Copy the message to a string so that we can pass it to the spam database
|
||||
// (the even messier alternative is a temporary file). Do it in a fashion
|
||||
// which allows NUL bytes in the string. This method of course limits the
|
||||
// message size to a few hundred megabytes. If we're using header mode,
|
||||
// only read the header rather than the full message.
|
||||
|
||||
if (fHeaderOnly) {
|
||||
// Read just the header, it ends with an empty CRLF line.
|
||||
dataStreamPntr->Seek (0, SEEK_SET);
|
||||
while ((errorCode = dataStreamPntr->Read (&tempChar, 1)) == 1) {
|
||||
headerString.Append (tempChar, 1);
|
||||
headerLength = headerString.Length();
|
||||
if (headerLength >= 4 && strcmp (headerString.String() +
|
||||
headerLength - 4, "\r\n\r\n") == 0)
|
||||
break;
|
||||
}
|
||||
if (errorCode < 0)
|
||||
goto ErrorExit;
|
||||
|
||||
dataSize = headerString.Length();
|
||||
stringBuffer = new char [dataSize + 1];
|
||||
memcpy (stringBuffer, headerString.String(), dataSize);
|
||||
stringBuffer[dataSize] = 0;
|
||||
} else {
|
||||
// Read the whole file. The seek to the end may take a while since
|
||||
// that triggers downloading of the entire message (and caching in a
|
||||
// slave file - see the MessageIO class).
|
||||
dataSize = dataStreamPntr->Seek (0, SEEK_END);
|
||||
if (dataSize <= 0)
|
||||
goto ErrorExit;
|
||||
|
||||
try {
|
||||
stringBuffer = new char [dataSize + 1];
|
||||
} catch (...) {
|
||||
errorCode = ENOMEM;
|
||||
goto ErrorExit;
|
||||
}
|
||||
|
||||
dataStreamPntr->Seek (0, SEEK_SET);
|
||||
amountRead = dataStreamPntr->Read (stringBuffer, dataSize);
|
||||
if (amountRead != dataSize)
|
||||
goto ErrorExit;
|
||||
stringBuffer[dataSize] = 0; // Add an end of string NUL, just in case.
|
||||
}
|
||||
|
||||
// Send off a scripting command to the database server, asking it to
|
||||
// evaluate the string for spaminess. Note that it can return ENOMSG
|
||||
// when there are no words (a good indicator of spam which is pure HTML
|
||||
// if you are using plain text only tokenization), so we could use that
|
||||
// as a spam marker too. Code copied for the reevaluate stuff below.
|
||||
|
||||
scriptingMessage.MakeEmpty ();
|
||||
scriptingMessage.what = B_SET_PROPERTY;
|
||||
scriptingMessage.AddSpecifier ("EvaluateString");
|
||||
errorCode = scriptingMessage.AddData ("data", B_STRING_TYPE,
|
||||
stringBuffer, dataSize + 1, false /* fixed size */);
|
||||
if (errorCode != B_OK)
|
||||
goto ErrorExit;
|
||||
replyMessage.MakeEmpty ();
|
||||
errorCode = fMessengerToServer.SendMessage (&scriptingMessage,
|
||||
&replyMessage);
|
||||
if (errorCode != B_OK
|
||||
|| replyMessage.FindInt32 ("error", &errorCode) != B_OK)
|
||||
goto ErrorExit; // Unable to read the return code.
|
||||
if (errorCode == ENOMSG && fNoWordsMeansSpam)
|
||||
spamRatio = fSpamCutoffRatio; // Yes, no words and that means spam.
|
||||
else if (errorCode != B_OK
|
||||
|| replyMessage.FindFloat ("result", &spamRatio) != B_OK)
|
||||
goto ErrorExit; // Classification failed in one of many ways.
|
||||
|
||||
// If we are auto-training, feed back the message to the server as a
|
||||
// training example (don't train if it is uncertain). Also redo the
|
||||
// evaluation after training.
|
||||
|
||||
if (fAutoTraining) {
|
||||
if (spamRatio >= fSpamCutoffRatio || spamRatio < fGenuineCutoffRatio) {
|
||||
scriptingMessage.MakeEmpty ();
|
||||
scriptingMessage.what = B_SET_PROPERTY;
|
||||
scriptingMessage.AddSpecifier ((spamRatio >= fSpamCutoffRatio)
|
||||
? "SpamString" : "GenuineString");
|
||||
errorCode = scriptingMessage.AddData ("data", B_STRING_TYPE,
|
||||
stringBuffer, dataSize + 1, false /* fixed size */);
|
||||
if (errorCode != B_OK)
|
||||
goto ErrorExit;
|
||||
replyMessage.MakeEmpty ();
|
||||
errorCode = fMessengerToServer.SendMessage (&scriptingMessage,
|
||||
&replyMessage);
|
||||
if (errorCode != B_OK
|
||||
|| replyMessage.FindInt32 ("error", &errorCode) != B_OK)
|
||||
goto ErrorExit; // Unable to read the return code.
|
||||
if (errorCode != B_OK)
|
||||
goto ErrorExit; // Failed to set a good example.
|
||||
}
|
||||
|
||||
// Note the kind of example made so that the user doesn't reclassify
|
||||
// the message twice (the spam server looks for this attribute).
|
||||
|
||||
classificationString =
|
||||
(spamRatio >= fSpamCutoffRatio)
|
||||
? "Spam"
|
||||
: ((spamRatio < fGenuineCutoffRatio) ? "Genuine" : "Uncertain");
|
||||
if (nodeForOutputFileInitialised)
|
||||
nodeForOutputFile.WriteAttr ("MAIL:classification", B_STRING_TYPE,
|
||||
0 /* offset */, classificationString,
|
||||
strlen (classificationString) + 1);
|
||||
|
||||
// Now that the database has changed due to training, recompute the
|
||||
// spam ratio. Hopefully it will have become more extreme in the
|
||||
// correct direction (not switched from being spam to being genuine).
|
||||
// Code copied from above.
|
||||
|
||||
scriptingMessage.MakeEmpty ();
|
||||
scriptingMessage.what = B_SET_PROPERTY;
|
||||
scriptingMessage.AddSpecifier ("EvaluateString");
|
||||
errorCode = scriptingMessage.AddData ("data", B_STRING_TYPE,
|
||||
stringBuffer, dataSize + 1, false /* fixed size */);
|
||||
if (errorCode != B_OK)
|
||||
goto ErrorExit;
|
||||
replyMessage.MakeEmpty ();
|
||||
errorCode = fMessengerToServer.SendMessage (&scriptingMessage,
|
||||
&replyMessage);
|
||||
if (errorCode != B_OK
|
||||
|| replyMessage.FindInt32 ("error", &errorCode) != B_OK)
|
||||
goto ErrorExit; // Unable to read the return code.
|
||||
if (errorCode == ENOMSG && fNoWordsMeansSpam)
|
||||
spamRatio = fSpamCutoffRatio; // Yes, no words and that means spam.
|
||||
else if (errorCode != B_OK
|
||||
|| replyMessage.FindFloat ("result", &spamRatio) != B_OK)
|
||||
goto ErrorExit; // Classification failed in one of many ways.
|
||||
}
|
||||
|
||||
// Store the spam ratio in an attribute called MAIL:ratio_spam,
|
||||
// attached to the eventual output file.
|
||||
|
||||
if (nodeForOutputFileInitialised)
|
||||
nodeForOutputFile.WriteAttr ("MAIL:ratio_spam",
|
||||
B_FLOAT_TYPE, 0 /* offset */, &spamRatio, sizeof (spamRatio));
|
||||
|
||||
// Also add it to the subject, if requested.
|
||||
|
||||
if (fAddSpamToSubject
|
||||
&& spamRatio >= fSpamCutoffRatio
|
||||
&& io_headers->FindString ("Subject", &oldSubjectStringPntr) == B_OK) {
|
||||
newSubjectString.SetTo ("[Spam ");
|
||||
sprintf (percentageString, "%05.2f", spamRatio * 100.0);
|
||||
newSubjectString << percentageString << "%] ";
|
||||
newSubjectString << oldSubjectStringPntr;
|
||||
io_headers->ReplaceString ("Subject", newSubjectString);
|
||||
}
|
||||
|
||||
// Beep using different sounds for spam and genuine, as Jeremy Friesner
|
||||
// nudged me to get around to implementing. And add uncertain to that, as
|
||||
// "BiPolar" suggested. If the user doesn't want to hear the sound, they
|
||||
// can turn it off in the system sound preferences.
|
||||
|
||||
if (spamRatio >= fSpamCutoffRatio) {
|
||||
system_beep (kAGMSBayesBeepSpamName);
|
||||
} else if (spamRatio < fGenuineCutoffRatio) {
|
||||
system_beep (kAGMSBayesBeepGenuineName);
|
||||
} else {
|
||||
system_beep (kAGMSBayesBeepUncertainName);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
|
||||
ErrorExit:
|
||||
fprintf (stderr, "Error exit from "
|
||||
"AGMSBayesianSpamFilter::ProcessMailMessage, code maybe %ld (%s).\n",
|
||||
errorCode, strerror (errorCode));
|
||||
delete [] stringBuffer;
|
||||
return B_OK; // Not MD_ERROR so the message doesn't get left on server.
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
descriptive_name (
|
||||
BMessage *settings,
|
||||
char *buffer)
|
||||
{
|
||||
bool addMarker = false;
|
||||
bool autoTraining = true;
|
||||
float cutoffRatio = 0.99f;
|
||||
bool tempBool;
|
||||
float tempFloat;
|
||||
|
||||
if (settings != NULL) {
|
||||
if (settings->FindBool ("AddMarkerToSubject", &tempBool) == B_OK)
|
||||
addMarker = tempBool;
|
||||
if (settings->FindBool ("AutoTraining", &tempBool) == B_OK)
|
||||
autoTraining = tempBool;
|
||||
if (settings->FindFloat ("SpamCutoffRatio", &tempFloat) == B_OK)
|
||||
cutoffRatio = tempFloat;
|
||||
}
|
||||
|
||||
sprintf (buffer, "Spam >= %05.3f", (double) cutoffRatio);
|
||||
if (addMarker)
|
||||
strcat (buffer, ", Mark Subject");
|
||||
if (autoTraining)
|
||||
strcat (buffer, ", Self-training");
|
||||
strcat (buffer, ".");
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
BMailFilter *
|
||||
instantiate_mailfilter (
|
||||
BMessage *settings,
|
||||
BMailChainRunner *)
|
||||
{
|
||||
return new AGMSBayesianSpamFilter (settings);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef AGMS_BAYESIAN_SPAM_FILTER_H
|
||||
#define AGMS_BAYESIAN_SPAM_FILTER_H
|
||||
/******************************************************************************
|
||||
* AGMSBayesianSpamFilter - Uses Bayesian statistics to evaluate the spaminess
|
||||
* of a message. The evaluation is done by a separate server, this add-on just
|
||||
* gets the text and uses scripting commands to get an evaluation from the
|
||||
* server. If the server isn't running, it will be found and started up. Once
|
||||
* the evaluation has been received, it is added to the message as an attribute
|
||||
* and optionally as an addition to the subject. Some other add-on later in
|
||||
* the pipeline will use the attribute to delete the message or move it to some
|
||||
* other folder.
|
||||
*
|
||||
* Public Domain 2002, by Alexander G. M. Smith, no warranty.
|
||||
*
|
||||
* $Log: SpamFilter.h,v $
|
||||
* Revision 1.1 2004/10/30 22:23:26 brunoga
|
||||
* AGMS Spam Filter.
|
||||
*
|
||||
* Revision 1.8 2004/09/20 15:57:30 nwhitehorn
|
||||
* Mostly updated the tree to Be/Haiku style identifier naming conventions. I have a few more things to work out, mostly in mail_util.h, and then I'm proceeding to jamify the build system. Then we go into Haiku CVS.
|
||||
*
|
||||
* Revision 1.7 2003/05/27 17:12:59 nwhitehorn
|
||||
* Massive refactoring of the Protocol/ChainRunner/Filter system. You can probably
|
||||
* examine its scope by examining the number of files changed. Regardless, this is
|
||||
* preparation for lots of new features, and REAL WORKING IMAP. Yes, you heard me.
|
||||
* Enjoy, and prepare for bugs (although I've fixed all the ones I've found, I susp
|
||||
* ect there are some memory leaks in ChainRunner).
|
||||
*
|
||||
* Revision 1.6 2003/02/08 21:54:17 agmsmith
|
||||
* Updated the AGMSBayesianSpamServer documentation to match the current
|
||||
* version. Also removed the Beep options from the spam filter, now they
|
||||
* are turned on or off in the system sound preferences.
|
||||
*
|
||||
* Revision 1.5 2002/12/18 02:27:45 agmsmith
|
||||
* Added uncertain classification as suggested by BiPolar.
|
||||
*
|
||||
* Revision 1.4 2002/12/12 00:56:28 agmsmith
|
||||
* Added some new spam filter options - self training (not implemented yet)
|
||||
* and a button to edit the server settings.
|
||||
*
|
||||
* Revision 1.3 2002/11/28 20:20:57 agmsmith
|
||||
* Now checks if the spam database is running in headers only mode, and
|
||||
* then only downloads headers if that is the case.
|
||||
*
|
||||
* Revision 1.2 2002/11/10 19:36:27 agmsmith
|
||||
* Retry launching server a few times, but not too many.
|
||||
*
|
||||
* Revision 1.1 2002/11/03 02:06:15 agmsmith
|
||||
* Added initial version.
|
||||
*
|
||||
* Revision 1.5 2002/10/21 16:13:59 agmsmith
|
||||
* Added option to have no words mean spam.
|
||||
*
|
||||
* Revision 1.4 2002/10/11 20:01:28 agmsmith
|
||||
* Added sound effects (system beep) for genuine and spam, plus config option
|
||||
* for it.
|
||||
*
|
||||
* Revision 1.3 2002/09/23 19:14:13 agmsmith
|
||||
* Added an option to have the server quit when done.
|
||||
*
|
||||
* Revision 1.2 2002/09/23 03:33:34 agmsmith
|
||||
* First working version, with cutoff ratio and subject modification,
|
||||
* and an attribute added if a patch is made to the Folder filter.
|
||||
*
|
||||
* Revision 1.1 2002/09/21 20:47:57 agmsmith
|
||||
* Initial revision
|
||||
*/
|
||||
|
||||
#include <Message.h>
|
||||
#include <List.h>
|
||||
#include <MailAddon.h>
|
||||
|
||||
|
||||
class AGMSBayesianSpamFilter : public BMailFilter {
|
||||
public:
|
||||
AGMSBayesianSpamFilter (BMessage *settings);
|
||||
virtual ~AGMSBayesianSpamFilter ();
|
||||
|
||||
virtual status_t InitCheck (BString* out_message = NULL);
|
||||
|
||||
virtual status_t ProcessMailMessage (BPositionIO** io_message,
|
||||
BEntry* io_entry,
|
||||
BMessage* io_headers,
|
||||
BPath* io_folder,
|
||||
const char* io_uid);
|
||||
|
||||
private:
|
||||
bool fAddSpamToSubject;
|
||||
bool fAutoTraining;
|
||||
float fGenuineCutoffRatio;
|
||||
bool fHeaderOnly;
|
||||
int fLaunchAttemptCount;
|
||||
BMessenger fMessengerToServer;
|
||||
bool fNoWordsMeansSpam;
|
||||
bool fQuitServerWhenFinished;
|
||||
float fSpamCutoffRatio;
|
||||
};
|
||||
|
||||
#endif /* AGMS_BAYESIAN_SPAM_FILTER_H */
|
||||
@@ -0,0 +1,401 @@
|
||||
/******************************************************************************
|
||||
* AGMSBayesianSpamFilter's configuration view. Lets the user change various
|
||||
* settings related to the add-on, but not the server.
|
||||
*
|
||||
* $Log: SpamFilterConfig.cpp,v $
|
||||
* Revision 1.1 2004/10/30 22:23:26 brunoga
|
||||
* AGMS Spam Filter.
|
||||
*
|
||||
* Revision 1.9 2004/09/20 15:57:30 nwhitehorn
|
||||
* Mostly updated the tree to Be/Haiku style identifier naming conventions. I have a few more things to work out, mostly in mail_util.h, and then I'm proceeding to jamify the build system. Then we go into Haiku CVS.
|
||||
*
|
||||
* Revision 1.8 2003/07/08 21:12:47 agmsmith
|
||||
* Changed other spam filter defaults to values I find useful.
|
||||
*
|
||||
* Revision 1.7 2003/07/08 20:56:40 agmsmith
|
||||
* Turn on auto-training for the spam filter by default.
|
||||
*
|
||||
* Revision 1.6 2003/02/08 21:54:17 agmsmith
|
||||
* Updated the AGMSBayesianSpamServer documentation to match the current
|
||||
* version. Also removed the Beep options from the spam filter, now they
|
||||
* are turned on or off in the system sound preferences.
|
||||
*
|
||||
* Revision 1.5 2002/12/18 02:27:45 agmsmith
|
||||
* Added uncertain classification as suggested by BiPolar.
|
||||
*
|
||||
* Revision 1.4 2002/12/16 16:03:27 agmsmith
|
||||
* Changed spam cutoff to 0.95 to work with default Chi-Squared scoring.
|
||||
*
|
||||
* Revision 1.3 2002/12/13 22:04:43 agmsmith
|
||||
* Changed default to turn on the Spam marker in the subject.
|
||||
*
|
||||
* Revision 1.2 2002/12/12 00:56:28 agmsmith
|
||||
* Added some new spam filter options - self training (not implemented yet)
|
||||
* and a button to edit the server settings.
|
||||
*
|
||||
* Revision 1.1 2002/11/03 02:06:15 agmsmith
|
||||
* Added initial version.
|
||||
*
|
||||
* Revision 1.7 2002/10/21 16:13:27 agmsmith
|
||||
* Added option to have no words mean spam.
|
||||
*
|
||||
* Revision 1.6 2002/10/11 20:01:28 agmsmith
|
||||
* Added sound effects (system beep) for genuine and spam, plus config option for it.
|
||||
*
|
||||
* Revision 1.5 2002/10/01 00:45:34 agmsmith
|
||||
* Changed default spam ratio to 0.56 from 0.9, for use with
|
||||
* the Gary Robinson method in AGMSBayesianSpamServer 1.49.
|
||||
*
|
||||
* Revision 1.4 2002/09/23 19:14:13 agmsmith
|
||||
* Added an option to have the server quit when done.
|
||||
*
|
||||
* Revision 1.3 2002/09/23 03:33:34 agmsmith
|
||||
* First working version, with cutoff ratio and subject modification,
|
||||
* and an attribute added if a patch is made to the Folder filter.
|
||||
*
|
||||
* Revision 1.2 2002/09/21 20:57:22 agmsmith
|
||||
* Fixed bugs so now it compiles.
|
||||
*
|
||||
* Revision 1.1 2002/09/21 20:48:11 agmsmith
|
||||
* Initial revision
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <Alert.h>
|
||||
#include <Button.h>
|
||||
#include <CheckBox.h>
|
||||
#include <Message.h>
|
||||
#include <Messenger.h>
|
||||
#include <Roster.h>
|
||||
#include <StringView.h>
|
||||
#include <TextControl.h>
|
||||
|
||||
#include <MailAddon.h>
|
||||
#include <FileConfigView.h>
|
||||
|
||||
static const char *kServerSignature =
|
||||
"application/x-vnd.agmsmith.AGMSBayesianSpamServer";
|
||||
|
||||
class AGMSBayesianSpamFilterConfig : public BView {
|
||||
public:
|
||||
AGMSBayesianSpamFilterConfig (BMessage *settings);
|
||||
|
||||
virtual void MessageReceived (BMessage *msg);
|
||||
virtual void AttachedToWindow ();
|
||||
virtual status_t Archive (BMessage *into, bool deep = true) const;
|
||||
virtual void GetPreferredSize (float *width, float *height);
|
||||
|
||||
private:
|
||||
void ShowSpamServerConfigurationWindow ();
|
||||
|
||||
bool fAddSpamToSubject;
|
||||
BCheckBox *fAddSpamToSubjectCheckBoxPntr;
|
||||
bool fAutoTraining;
|
||||
BCheckBox *fAutoTrainingCheckBoxPntr;
|
||||
float fGenuineCutoffRatio;
|
||||
BTextControl *fGenuineCutoffRatioTextBoxPntr;
|
||||
bool fNoWordsMeansSpam;
|
||||
BCheckBox *fNoWordsMeansSpamCheckBoxPntr;
|
||||
bool fQuitServerWhenFinished;
|
||||
BCheckBox *fQuitServerWhenFinishedCheckBoxPntr;
|
||||
BButton *fServerSettingsButtonPntr;
|
||||
float fSpamCutoffRatio;
|
||||
BTextControl *fSpamCutoffRatioTextBoxPntr;
|
||||
static const uint32 kAddSpamToSubjectPressed = 'ASbj';
|
||||
static const uint32 kAutoTrainingPressed = 'AuTr';
|
||||
static const uint32 kNoWordsMeansSpam = 'NoWd';
|
||||
static const uint32 kQuitWhenFinishedPressed = 'QuWF';
|
||||
static const uint32 kServerSettingsPressed = 'SrvS';
|
||||
};
|
||||
|
||||
|
||||
AGMSBayesianSpamFilterConfig::AGMSBayesianSpamFilterConfig (BMessage *settings)
|
||||
: BView (BRect (0,0,260,130), "agmsbayesianspamfilter_config",
|
||||
B_FOLLOW_LEFT | B_FOLLOW_TOP, 0),
|
||||
fAddSpamToSubject (false),
|
||||
fAddSpamToSubjectCheckBoxPntr (NULL),
|
||||
fAutoTraining (true),
|
||||
fAutoTrainingCheckBoxPntr (NULL),
|
||||
fGenuineCutoffRatio (0.01f),
|
||||
fGenuineCutoffRatioTextBoxPntr (NULL),
|
||||
fNoWordsMeansSpam (true),
|
||||
fNoWordsMeansSpamCheckBoxPntr (NULL),
|
||||
fQuitServerWhenFinished (false),
|
||||
fQuitServerWhenFinishedCheckBoxPntr (NULL),
|
||||
fServerSettingsButtonPntr (NULL),
|
||||
fSpamCutoffRatio (0.99f),
|
||||
fSpamCutoffRatioTextBoxPntr (NULL)
|
||||
{
|
||||
bool tempBool;
|
||||
float tempFloat;
|
||||
|
||||
if (settings->FindBool ("AddMarkerToSubject", &tempBool) == B_OK)
|
||||
fAddSpamToSubject = tempBool;
|
||||
if (settings->FindBool ("AutoTraining", &tempBool) == B_OK)
|
||||
fAutoTraining = tempBool;
|
||||
if (settings->FindFloat ("GenuineCutoffRatio", &tempFloat) == B_OK)
|
||||
fGenuineCutoffRatio = tempFloat;
|
||||
if (settings->FindBool ("NoWordsMeansSpam", &tempBool) == B_OK)
|
||||
fNoWordsMeansSpam = tempBool;
|
||||
if (settings->FindBool ("QuitServerWhenFinished", &tempBool) == B_OK)
|
||||
fQuitServerWhenFinished = tempBool;
|
||||
if (settings->FindFloat ("SpamCutoffRatio", &tempFloat) == B_OK)
|
||||
fSpamCutoffRatio = tempFloat;
|
||||
}
|
||||
|
||||
|
||||
void AGMSBayesianSpamFilterConfig::AttachedToWindow ()
|
||||
{
|
||||
float deltaX;
|
||||
BStringView *labelViewPntr;
|
||||
char numberString [30];
|
||||
BRect tempRect;
|
||||
char *tempStringPntr;
|
||||
|
||||
SetViewColor (ui_color (B_PANEL_BACKGROUND_COLOR));
|
||||
|
||||
// Make the checkbox for choosing whether the spam is marked by a
|
||||
// modification to the subject of the mail message.
|
||||
|
||||
tempRect = Bounds ();
|
||||
fAddSpamToSubjectCheckBoxPntr = new BCheckBox (
|
||||
tempRect,
|
||||
"AddToSubject",
|
||||
"Add \"[Spam %]\" in front of Subject.",
|
||||
new BMessage (kAddSpamToSubjectPressed));
|
||||
AddChild (fAddSpamToSubjectCheckBoxPntr);
|
||||
fAddSpamToSubjectCheckBoxPntr->ResizeToPreferred ();
|
||||
fAddSpamToSubjectCheckBoxPntr->SetValue (fAddSpamToSubject);
|
||||
fAddSpamToSubjectCheckBoxPntr->SetTarget (this);
|
||||
|
||||
tempRect = Bounds ();
|
||||
tempRect.top = fAddSpamToSubjectCheckBoxPntr->Frame().bottom + 1;
|
||||
tempRect.bottom = tempRect.top + 20;
|
||||
|
||||
// Add the checkbox on the right for the no words means spam option.
|
||||
|
||||
fNoWordsMeansSpamCheckBoxPntr = new BCheckBox (
|
||||
tempRect,
|
||||
"NoWordsMeansSpam",
|
||||
"or no words found.",
|
||||
new BMessage (kNoWordsMeansSpam));
|
||||
AddChild (fNoWordsMeansSpamCheckBoxPntr);
|
||||
fNoWordsMeansSpamCheckBoxPntr->ResizeToPreferred ();
|
||||
fNoWordsMeansSpamCheckBoxPntr->MoveBy (
|
||||
floorf (tempRect.right - fNoWordsMeansSpamCheckBoxPntr->Frame().right),
|
||||
0.0);
|
||||
fNoWordsMeansSpamCheckBoxPntr->SetValue (fNoWordsMeansSpam);
|
||||
fNoWordsMeansSpamCheckBoxPntr->SetTarget (this);
|
||||
|
||||
// Add the box displaying the spam cutoff ratio to the left, in the space
|
||||
// remaining between the left edge and the no words checkbox.
|
||||
|
||||
tempRect.right = fNoWordsMeansSpamCheckBoxPntr->Frame().left -
|
||||
be_plain_font->StringWidth ("a");
|
||||
tempStringPntr = "Spam above:";
|
||||
sprintf (numberString, "%06.4f", (double) fSpamCutoffRatio);
|
||||
fSpamCutoffRatioTextBoxPntr = new BTextControl (
|
||||
tempRect,
|
||||
"spamcutoffratio",
|
||||
tempStringPntr,
|
||||
numberString,
|
||||
NULL /* BMessage */);
|
||||
AddChild (fSpamCutoffRatioTextBoxPntr);
|
||||
fSpamCutoffRatioTextBoxPntr->SetDivider (
|
||||
be_plain_font->StringWidth (tempStringPntr) +
|
||||
1 * be_plain_font->StringWidth ("a"));
|
||||
|
||||
tempRect = Bounds ();
|
||||
tempRect.top = fSpamCutoffRatioTextBoxPntr->Frame().bottom + 1;
|
||||
tempRect.bottom = tempRect.top + 20;
|
||||
|
||||
// Add the box displaying the genuine cutoff ratio, on a line by itself.
|
||||
|
||||
tempStringPntr = "Genuine below and uncertain above:";
|
||||
sprintf (numberString, "%06.4f", (double) fGenuineCutoffRatio);
|
||||
fGenuineCutoffRatioTextBoxPntr = new BTextControl (
|
||||
tempRect,
|
||||
"genuinecutoffratio",
|
||||
tempStringPntr,
|
||||
numberString,
|
||||
NULL /* BMessage */);
|
||||
AddChild (fGenuineCutoffRatioTextBoxPntr);
|
||||
fGenuineCutoffRatioTextBoxPntr->SetDivider (
|
||||
be_plain_font->StringWidth (tempStringPntr) +
|
||||
1 * be_plain_font->StringWidth ("a"));
|
||||
|
||||
tempRect = Bounds ();
|
||||
tempRect.top = fGenuineCutoffRatioTextBoxPntr->Frame().bottom + 1;
|
||||
tempRect.bottom = tempRect.top + 20;
|
||||
|
||||
// Checkbox for automatically training on incoming mail.
|
||||
|
||||
fAutoTrainingCheckBoxPntr = new BCheckBox (
|
||||
tempRect,
|
||||
"autoTraining",
|
||||
"Self-training on Incoming Messages.",
|
||||
new BMessage (kAutoTrainingPressed));
|
||||
AddChild (fAutoTrainingCheckBoxPntr);
|
||||
fAutoTrainingCheckBoxPntr->ResizeToPreferred ();
|
||||
fAutoTrainingCheckBoxPntr->SetValue (fAutoTraining);
|
||||
fAutoTrainingCheckBoxPntr->SetTarget (this);
|
||||
|
||||
tempRect = Bounds ();
|
||||
tempRect.top = fAutoTrainingCheckBoxPntr->Frame().bottom + 1;
|
||||
tempRect.bottom = tempRect.top + 20;
|
||||
|
||||
// Button for editing the server settings.
|
||||
|
||||
fServerSettingsButtonPntr = new BButton (
|
||||
tempRect,
|
||||
"serverSettings",
|
||||
"Edit Server Settings",
|
||||
new BMessage (kServerSettingsPressed));
|
||||
AddChild (fServerSettingsButtonPntr);
|
||||
fServerSettingsButtonPntr->ResizeToPreferred ();
|
||||
fServerSettingsButtonPntr->SetTarget (this);
|
||||
|
||||
tempRect = Bounds ();
|
||||
tempRect.top = fServerSettingsButtonPntr->Frame().bottom + 1;
|
||||
tempRect.bottom = tempRect.top + 20;
|
||||
|
||||
// Checkbox for closing the server when done.
|
||||
|
||||
fQuitServerWhenFinishedCheckBoxPntr = new BCheckBox (
|
||||
tempRect,
|
||||
"quitWhenFinished",
|
||||
"Close AGMSBayesianSpamServer when Finished.",
|
||||
new BMessage (kQuitWhenFinishedPressed));
|
||||
AddChild (fQuitServerWhenFinishedCheckBoxPntr);
|
||||
fQuitServerWhenFinishedCheckBoxPntr->ResizeToPreferred ();
|
||||
fQuitServerWhenFinishedCheckBoxPntr->SetValue (fQuitServerWhenFinished);
|
||||
fQuitServerWhenFinishedCheckBoxPntr->SetTarget (this);
|
||||
|
||||
tempRect = Bounds ();
|
||||
tempRect.top = fQuitServerWhenFinishedCheckBoxPntr->Frame().bottom + 1;
|
||||
tempRect.bottom = tempRect.top + 20;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
AGMSBayesianSpamFilterConfig::Archive (BMessage *into, bool deep) const
|
||||
{
|
||||
status_t errorCode;
|
||||
float tempFloat;
|
||||
|
||||
into->MakeEmpty();
|
||||
errorCode = into->AddBool ("AddMarkerToSubject", fAddSpamToSubject);
|
||||
|
||||
if (errorCode == B_OK)
|
||||
errorCode = into->AddBool ("AutoTraining", fAutoTraining);
|
||||
|
||||
if (errorCode == B_OK)
|
||||
errorCode = into->AddBool ("QuitServerWhenFinished", fQuitServerWhenFinished);
|
||||
|
||||
if (errorCode == B_OK)
|
||||
errorCode = into->AddBool ("NoWordsMeansSpam", fNoWordsMeansSpam);
|
||||
|
||||
if (errorCode == B_OK) {
|
||||
tempFloat = fGenuineCutoffRatio;
|
||||
if (fGenuineCutoffRatioTextBoxPntr != NULL)
|
||||
tempFloat = atof (fGenuineCutoffRatioTextBoxPntr->Text());
|
||||
errorCode = into->AddFloat ("GenuineCutoffRatio", tempFloat);
|
||||
}
|
||||
|
||||
if (errorCode == B_OK) {
|
||||
tempFloat = fSpamCutoffRatio;
|
||||
if (fSpamCutoffRatioTextBoxPntr != NULL)
|
||||
tempFloat = atof (fSpamCutoffRatioTextBoxPntr->Text());
|
||||
errorCode = into->AddFloat ("SpamCutoffRatio", tempFloat);
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AGMSBayesianSpamFilterConfig::GetPreferredSize (float *width, float *height) {
|
||||
*width = 260;
|
||||
*height = 130;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AGMSBayesianSpamFilterConfig::MessageReceived (BMessage *msg)
|
||||
{
|
||||
switch (msg->what)
|
||||
{
|
||||
case kAddSpamToSubjectPressed:
|
||||
fAddSpamToSubject = fAddSpamToSubjectCheckBoxPntr->Value ();
|
||||
break;
|
||||
case kAutoTrainingPressed:
|
||||
fAutoTraining = fAutoTrainingCheckBoxPntr->Value ();
|
||||
break;
|
||||
case kNoWordsMeansSpam:
|
||||
fNoWordsMeansSpam = fNoWordsMeansSpamCheckBoxPntr->Value ();
|
||||
break;
|
||||
case kQuitWhenFinishedPressed:
|
||||
fQuitServerWhenFinished =
|
||||
fQuitServerWhenFinishedCheckBoxPntr->Value ();
|
||||
break;
|
||||
case kServerSettingsPressed:
|
||||
ShowSpamServerConfigurationWindow ();
|
||||
break;
|
||||
default:
|
||||
BView::MessageReceived (msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AGMSBayesianSpamFilterConfig::ShowSpamServerConfigurationWindow () {
|
||||
status_t errorCode = B_OK;
|
||||
BMessage maximizeCommand;
|
||||
BMessenger messengerToServer;
|
||||
BMessage replyMessage;
|
||||
team_id serverTeam;
|
||||
|
||||
// Make sure the server is running.
|
||||
if (!be_roster->IsRunning (kServerSignature)) {
|
||||
errorCode = be_roster->Launch (kServerSignature);
|
||||
if (errorCode != B_OK)
|
||||
goto ErrorExit;
|
||||
}
|
||||
|
||||
// Set up the messenger to the database server.
|
||||
serverTeam = be_roster->TeamFor (kServerSignature);
|
||||
if (serverTeam < 0)
|
||||
goto ErrorExit;
|
||||
messengerToServer =
|
||||
BMessenger (kServerSignature, serverTeam, &errorCode);
|
||||
if (!messengerToServer.IsValid ())
|
||||
goto ErrorExit;
|
||||
|
||||
// Wait for the server to finish starting up, and for it to create the window.
|
||||
snooze (2000000);
|
||||
|
||||
// Tell it to show its main window, in case it is hidden in server mode.
|
||||
maximizeCommand.what = B_SET_PROPERTY;
|
||||
maximizeCommand.AddBool ("data", false);
|
||||
maximizeCommand.AddSpecifier ("Minimize");
|
||||
maximizeCommand.AddSpecifier ("Window", 0L);
|
||||
errorCode = messengerToServer.SendMessage (&maximizeCommand, &replyMessage);
|
||||
if (errorCode != B_OK)
|
||||
goto ErrorExit;
|
||||
return; // Successful.
|
||||
|
||||
ErrorExit:
|
||||
(new BAlert ("SpamFilterConfig Error", "Sorry, unable to launch the "
|
||||
"AGMSBayesianSpamServer program to let you edit the server "
|
||||
"settings.", "Close"))->Go ();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
BView *
|
||||
instantiate_config_panel (BMessage *settings, BMessage *metadata)
|
||||
{
|
||||
return new AGMSBayesianSpamFilterConfig (settings);
|
||||
}
|
||||
Reference in New Issue
Block a user