#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>
using namespace std;
#include <Alert.h>
#include <Application.h>
#include <Beep.h>
#include <Button.h>
#include <CheckBox.h>
#include <Cursor.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <fs_index.h>
#include <fs_info.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <Message.h>
#include <MessageQueue.h>
#include <MessageRunner.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Picture.h>
#include <PictureButton.h>
#include <Point.h>
#include <Polygon.h>
#include <PopUpMenu.h>
#include <PropertyInfo.h>
#include <RadioButton.h>
#include <Resources.h>
#include <Screen.h>
#include <ScrollBar.h>
#include <String.h>
#include <StringView.h>
#include <TextControl.h>
#include <View.h>
#include <MailMessage.h>
#include <MailAttachment.h>
static float g_MarginBetweenControls;
static float g_LineOfTextHeight;
static float g_StringViewHeight;
static float g_ButtonHeight;
static float g_CheckBoxHeight;
static float g_RadioButtonHeight;
static float g_PopUpMenuHeight;
static float g_TextBoxHeight;
static const char *g_ABSAppSignature =
"application/x-vnd.agmsmith.spamdbm";
static const char *g_ABSDatabaseFileMIMEType =
"text/x-vnd.agmsmith.spam_probability_database";
static const char *g_DefaultDatabaseFileName =
"SpamDBM Database";
static const char *g_DatabaseRecognitionString =
"Spam Database File";
static const char *g_AttributeNameClassification = "MAIL:classification";
static const char *g_AttributeNameSpamRatio = "MAIL:ratio_spam";
static const char *g_BeepGenuine = "SpamFilter-Genuine";
static const char *g_BeepSpam = "SpamFilter-Spam";
static const char *g_BeepUncertain = "SpamFilter-Uncertain";
static const char *g_ClassifiedSpam = "Spam";
static const char *g_ClassifiedGenuine = "Genuine";
static const char *g_DataName = "data";
static const char *g_ResultName = "result";
static const char *g_SettingsDirectoryName = "Mail";
static const char *g_SettingsFileName = "SpamDBM Settings";
static const uint32 g_SettingsWhatCode = 'SDBM';
static const char *g_BackupSuffix = ".backup %d";
static const int g_MaxBackups = 10;
static const size_t g_MaxWordLength = 50;
static const int g_MaxInterestingWords = 150;
static const double g_RobinsonS = 0.45;
static const double g_RobinsonX = 0.5;
static bool g_CommandLineMode;
static bool g_ServerMode;
static int g_QuitCountdown = -1;
static volatile bool g_AppReadyToRunCompleted = false;
static class CommanderLooper *g_CommanderLooperPntr = NULL;
static BMessenger *g_CommanderMessenger = NULL;
static BCursor *g_BusyCursor = NULL;
typedef enum PropertyNumbersEnum
{
PN_DATABASE_FILE = 0,
PN_SPAM,
PN_SPAM_STRING,
PN_GENUINE,
PN_GENUINE_STRING,
PN_UNCERTAIN,
PN_IGNORE_PREVIOUS_CLASSIFICATION,
PN_SERVER_MODE,
PN_FLUSH,
PN_PURGE_AGE,
PN_PURGE_POPULARITY,
PN_PURGE,
PN_OLDEST,
PN_EVALUATE,
PN_EVALUATE_STRING,
PN_RESET_TO_DEFAULTS,
PN_INSTALL_THINGS,
PN_TOKENIZE_MODE,
PN_SCORING_MODE,
PN_MAX
} PropertyNumbers;
static const char * g_PropertyNames [PN_MAX] =
{
"DatabaseFile",
"Spam",
"SpamString",
"Genuine",
"GenuineString",
"Uncertain",
"IgnorePreviousClassification",
"ServerMode",
"Flush",
"PurgeAge",
"PurgePopularity",
"Purge",
"Oldest",
"Evaluate",
"EvaluateString",
"ResetToDefaults",
"InstallThings",
"TokenizeMode",
"ScoringMode"
};
static struct property_info g_ScriptingPropertyList [] =
{
{g_PropertyNames[PN_DATABASE_FILE], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Get the pathname of the current database file. "
"The default name is something like B_USER_SETTINGS_DIRECTORY / "
"Mail / SpamDBM Database", PN_DATABASE_FILE,
{}, {}, {}},
{g_PropertyNames[PN_DATABASE_FILE], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Change the pathname of the database file to "
"use. It will automatically be converted to an absolute path name, "
"so make sure the parent directories exist before setting it. If it "
"doesn't exist, you'll have to use the create command next.",
PN_DATABASE_FILE, {}, {}, {}},
{g_PropertyNames[PN_DATABASE_FILE], {B_CREATE_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Creates a new empty database, will replace "
"the existing database file too.", PN_DATABASE_FILE, {}, {}, {}},
{g_PropertyNames[PN_DATABASE_FILE], {B_DELETE_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Deletes the database file and all backup copies "
"of that file too. Really only of use for uninstallers.",
PN_DATABASE_FILE, {}, {}, {}},
{g_PropertyNames[PN_DATABASE_FILE], {B_COUNT_PROPERTIES, 0},
{B_DIRECT_SPECIFIER, 0}, "Returns the number of words in the database.",
PN_DATABASE_FILE, {}, {}, {}},
{g_PropertyNames[PN_SPAM], {B_SET_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0},
"Adds the spam in the given file (specify full pathname to be safe) to "
"the database. The words in the files will be added to the list of words "
"in the database that identify spam messages. The files processed will "
"also have the attribute MAIL:classification added with a value of "
"\"Spam\" or \"Genuine\" as specified. They also have their spam ratio "
"attribute updated, as if you had also used the Evaluate command on "
"them. If they already have the MAIL:classification "
"attribute and it matches the new classification then they won't get "
"processed (and if it is different, they will get removed from the "
"statistics for the old class and added to the statistics for the new "
"one). You can turn off that behaviour with the "
"IgnorePreviousClassification property. The command line version lets "
"you specify more than one pathname.", PN_SPAM, {}, {}, {}},
{g_PropertyNames[PN_SPAM], {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0},
"Returns the number of spam messages in the database.", PN_SPAM,
{}, {}, {}},
{g_PropertyNames[PN_SPAM_STRING], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Adds the spam in the given string (assumed to "
"be the text of a whole e-mail message, not just a file name) to the "
"database.", PN_SPAM_STRING, {}, {}, {}},
{g_PropertyNames[PN_GENUINE], {B_SET_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0},
"Similar to adding spam except that the message file is added to the "
"genuine statistics.", PN_GENUINE, {}, {}, {}},
{g_PropertyNames[PN_GENUINE], {B_COUNT_PROPERTIES, 0},
{B_DIRECT_SPECIFIER, 0}, "Returns the number of genuine messages in the "
"database.", PN_GENUINE, {}, {}, {}},
{g_PropertyNames[PN_GENUINE_STRING], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Adds the genuine message in the given string "
"(assumed to be the text of a whole e-mail message, not just a file name) "
"to the database.", PN_GENUINE_STRING, {}, {}, {}},
{g_PropertyNames[PN_UNCERTAIN], {B_SET_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0},
"Similar to adding spam except that the message file is removed from the "
"database, undoing the previous classification. Obviously, it needs to "
"have been classified previously (using the file attributes) so it can "
"tell if it is removing spam or genuine words.", PN_UNCERTAIN, {}, {}, {}},
{g_PropertyNames[PN_IGNORE_PREVIOUS_CLASSIFICATION], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "If set to true then the previous classification "
"(which was saved as an attribute of the e-mail message file) will be "
"ignored, so that you can add the message to the database again. If set "
"to false (the normal case), the attribute will be examined, and if the "
"message has already been classified as what you claim it is, nothing "
"will be done. If it was misclassified, then the message will be removed "
"from the statistics for the old class and added to the stats for the "
"new classification you have requested.",
PN_IGNORE_PREVIOUS_CLASSIFICATION, {}, {}, {}},
{g_PropertyNames[PN_IGNORE_PREVIOUS_CLASSIFICATION], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Find out the current setting of the flag for "
"ignoring the previously recorded classification.",
PN_IGNORE_PREVIOUS_CLASSIFICATION, {}, {}, {}},
{g_PropertyNames[PN_SERVER_MODE], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "If set to true then error messages get printed "
"to the standard error stream rather than showing up in an alert box. "
"It also starts up with the window minimized.", PN_SERVER_MODE,
{}, {}, {}},
{g_PropertyNames[PN_SERVER_MODE], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Find out the setting of the server mode flag.",
PN_SERVER_MODE, {}, {}, {}},
{g_PropertyNames[PN_FLUSH], {B_EXECUTE_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Writes out the database file to disk, if it has "
"been updated in memory but hasn't been saved to disk. It will "
"automatically get written when the program exits, so this command is "
"mostly useful for server mode.", PN_FLUSH, {}, {}, {}},
{g_PropertyNames[PN_PURGE_AGE], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Sets the old age limit. Words which haven't "
"been updated since this many message additions to the database may be "
"deleted when you do a purge. A good value is 1000, meaning that if a "
"word hasn't appeared in the last 1000 spam/genuine messages, it will "
"be forgotten. Zero will purge all words, 1 will purge words not in "
"the last message added to the database, 2 will purge words not in the "
"last two messages added, and so on. This is mostly useful for "
"removing those one time words which are often hunks of binary garbage, "
"not real words. This acts in combination with the popularity limit; "
"both conditions have to be valid before the word gets deleted.",
PN_PURGE_AGE, {}, {}, {}},
{g_PropertyNames[PN_PURGE_AGE], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Gets the old age limit.", PN_PURGE_AGE,
{}, {}, {}},
{g_PropertyNames[PN_PURGE_POPULARITY], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Sets the popularity limit. Words which aren't "
"this popular may be deleted when you do a purge. A good value is 5, "
"which means that the word is safe from purging if it has been seen in 6 "
"or more e-mail messages. If it's only in 5 or less, then it may get "
"purged. The extreme is zero, where only words that haven't been seen "
"in any message are deleted (usually means no words). This acts in "
"combination with the old age limit; both conditions have to be valid "
"before the word gets deleted.", PN_PURGE_POPULARITY, {}, {}, {}},
{g_PropertyNames[PN_PURGE_POPULARITY], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Gets the purge popularity limit.",
PN_PURGE_POPULARITY, {}, {}, {}},
{g_PropertyNames[PN_PURGE], {B_EXECUTE_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Purges the old obsolete words from the "
"database, if they are old enough according to the age limit and also "
"unpopular enough according to the popularity limit.", PN_PURGE,
{}, {}, {}},
{g_PropertyNames[PN_OLDEST], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Gets the age of the oldest message in the "
"database. It's relative to the beginning of time, so you need to do "
"(total messages - age - 1) to see how many messages ago it was added.",
PN_OLDEST, {}, {}, {}},
{g_PropertyNames[PN_EVALUATE], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Evaluates a given file (by path name) to see "
"if it is spam or not. Returns the ratio of spam probability vs genuine "
"probability, 0.0 meaning completely genuine, 1.0 for completely spam. "
"Normally you should safely be able to consider it as spam if it is over "
"0.56 for the Robinson scoring method. For the ChiSquared method, the "
"numbers are near 0 for genuine, near 1 for spam, and anywhere in the "
"middle means it can't decide. The program attaches a MAIL:ratio_spam "
"attribute with the ratio as its "
"float32 value to the file. Also returns the top few interesting words "
"in \"words\" and the associated per-word probability ratios in "
"\"ratios\".", PN_EVALUATE, {}, {}, {}},
{g_PropertyNames[PN_EVALUATE_STRING], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Like Evaluate, but rather than a file name, "
"the string argument contains the entire text of the message to be "
"evaluated.", PN_EVALUATE_STRING, {}, {}, {}},
{g_PropertyNames[PN_RESET_TO_DEFAULTS], {B_EXECUTE_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Resets all the configuration options to the "
"default values, including the database name.", PN_RESET_TO_DEFAULTS,
{}, {}, {}},
{g_PropertyNames[PN_INSTALL_THINGS], {B_EXECUTE_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Creates indices for the MAIL:classification and "
"MAIL:ratio_spam attributes on all volumes which support BeOS queries, "
"identifies them to the system as e-mail related attributes (modifies "
"the text/x-email MIME type), and sets up the new MIME type "
"(text/x-vnd.agmsmith.spam_probability_database) for the database file. "
"Also registers names for the sound effects used by the separate filter "
"program (use the installsound BeOS program or the Sounds preferences "
"program to associate sound files with the names).", PN_INSTALL_THINGS,
{}, {}, {}},
{g_PropertyNames[PN_TOKENIZE_MODE], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Sets the method used for breaking up the "
"message into words. Use \"Whole\" for the whole file (also use it for "
"non-email files). The file isn't broken into parts; the whole thing is "
"converted into words, headers and attachments are just more raw data. "
"Well, not quite raw data since it converts quoted-printable codes "
"(equals sign followed by hex digits or end of line) to the equivalent "
"single characters. \"PlainText\" breaks the file into MIME components "
"and only looks at the ones which are of MIME type text/plain. "
"\"AnyText\" will look for words in all text/* things, including "
"text/html attachments. \"AllParts\" will decode all message components "
"and look for words in them, including binary attachments. "
"\"JustHeader\" will only look for words in the message header. "
"\"AllPartsAndHeader\", \"PlainTextAndHeader\" and \"AnyTextAndHeader\" "
"will also include the words from the message headers.", PN_TOKENIZE_MODE,
{}, {}, {}},
{g_PropertyNames[PN_TOKENIZE_MODE], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Gets the method used for breaking up the "
"message into words.", PN_TOKENIZE_MODE, {}, {}, {}},
{g_PropertyNames[PN_SCORING_MODE], {B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Sets the method used for combining the "
"probabilities of individual words into an overall score. "
"\"Robinson\" mode will use Gary Robinson's nth root of the product "
"method. It gives a nice range of values between 0 and 1 so you can "
"see shades of spaminess. The cutoff point between spam and genuine "
"varies depending on your database of words (0.56 was one point in "
"some experiments). \"ChiSquared\" mode will use chi-squared "
"statistics to evaluate the difference in probabilities that the lists "
"of word ratios are random. The result is very close to 0 for genuine "
"and very close to 1 for spam, and near the middle if it is uncertain.",
PN_SCORING_MODE, {}, {}, {}},
{g_PropertyNames[PN_SCORING_MODE], {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "Gets the method used for combining the "
"individual word ratios into an overall score.", PN_SCORING_MODE,
{}, {}, {}},
{ 0 }
};
typedef enum ScoringModeEnum
{
SM_ROBINSON = 0,
SM_CHISQUARED,
SM_MAX
} ScoringModes;
static const char * g_ScoringModeNames [SM_MAX] =
{
"Robinson",
"ChiSquared"
};
typedef enum TokenizeModeEnum
{
TM_WHOLE = 0,
TM_PLAIN_TEXT,
TM_PLAIN_TEXT_HEADER,
TM_ANY_TEXT,
TM_ANY_TEXT_HEADER,
TM_ALL_PARTS,
TM_ALL_PARTS_HEADER,
TM_JUST_HEADER,
TM_MAX
} TokenizeModes;
static const char * g_TokenizeModeNames [TM_MAX] =
{
"All",
"Plain text",
"Plain text and header",
"Any text",
"Any text and header",
"All parts",
"All parts and header",
"Just header"
};
typedef enum ClassificationTypesEnum
{
CL_GENUINE = 0,
CL_SPAM,
CL_UNCERTAIN,
CL_MAX
} ClassificationTypes;
static const char * g_ClassificationTypeNames [CL_MAX] =
{
g_ClassifiedGenuine,
g_ClassifiedSpam,
"Uncertain"
};
static BPoint g_UpLinePoints [] =
{
BPoint (8, 2 * (1)),
BPoint (14, 2 * (6)),
BPoint (10, 2 * (6)),
BPoint (10, 2 * (13)),
BPoint (6, 2 * (13)),
BPoint (6, 2 * (6)),
BPoint (2, 2 * (6))
};
static BPoint g_DownLinePoints [] =
{
BPoint (8, 2 * (14-1)),
BPoint (14, 2 * (14-6)),
BPoint (10, 2 * (14-6)),
BPoint (10, 2 * (14-13)),
BPoint (6, 2 * (14-13)),
BPoint (6, 2 * (14-6)),
BPoint (2, 2 * (14-6))
};
static BPoint g_UpPagePoints [] =
{
BPoint (8, 2 * (1)),
BPoint (13, 2 * (6)),
BPoint (10, 2 * (6)),
BPoint (14, 2 * (10)),
BPoint (10, 2 * (10)),
BPoint (10, 2 * (13)),
BPoint (6, 2 * (13)),
BPoint (6, 2 * (10)),
BPoint (2, 2 * (10)),
BPoint (6, 2 * (6)),
BPoint (3, 2 * (6))
};
static BPoint g_DownPagePoints [] =
{
BPoint (8, 2 * (14-1)),
BPoint (13, 2 * (14-6)),
BPoint (10, 2 * (14-6)),
BPoint (14, 2 * (14-10)),
BPoint (10, 2 * (14-10)),
BPoint (10, 2 * (14-13)),
BPoint (6, 2 * (14-13)),
BPoint (6, 2 * (14-10)),
BPoint (2, 2 * (14-10)),
BPoint (6, 2 * (14-6)),
BPoint (3, 2 * (14-6))
};
static bool g_SpaceCharacters [128];
typedef struct StatisticsStruct
{
uint32 age;
uint32 genuineCount;
uint32 spamCount;
} StatisticsRecord, *StatisticsPointer;
typedef map<string, StatisticsRecord> StatisticsMap;
class ClassificationChoicesWindow : public BWindow
{
public:
ClassificationChoicesWindow (BRect FrameRect,
const char *FileName, int NumberOfFiles);
virtual void MessageReceived (BMessage *MessagePntr);
void Go (bool *BulkModeSelectedPntr,
ClassificationTypes *ChoosenClassificationPntr);
static const uint32 MSG_CLASS_BUTTONS = 'ClB0';
static const uint32 MSG_CANCEL_BUTTON = 'Cncl';
static const uint32 MSG_BULK_CHECKBOX = 'BlkK';
private:
bool *m_BulkModeSelectedPntr;
ClassificationTypes *m_ChoosenClassificationPntr;
};
class ClassificationChoicesView : public BView
{
public:
ClassificationChoicesView (BRect FrameRect,
const char *FileName, int NumberOfFiles);
virtual void AttachedToWindow ();
virtual void GetPreferredSize (float *width, float *height);
private:
const char *m_FileName;
int m_NumberOfFiles;
float m_PreferredBottomY;
};
class CommanderLooper : public BLooper
{
public:
CommanderLooper ();
~CommanderLooper ();
virtual void MessageReceived (BMessage *MessagePntr);
void CommandArguments (int argc, char **argv);
void CommandReferences (BMessage *MessagePntr,
bool BulkMode = false,
ClassificationTypes BulkClassification = CL_GENUINE);
bool IsBusy ();
private:
void ProcessArgs (BMessage *MessagePntr);
void ProcessRefs (BMessage *MessagePntr);
static const uint32 MSG_COMMAND_ARGUMENTS = 'CArg';
static const uint32 MSG_COMMAND_FILE_REFS = 'CRef';
bool m_IsBusy;
};
class ControlsView : public BView
{
public:
ControlsView (BRect NewBounds);
~ControlsView ();
virtual void AttachedToWindow ();
virtual void FrameResized (float Width, float Height);
virtual void MessageReceived (BMessage *MessagePntr);
virtual void Pulse ();
private:
static const uint32 MSG_BROWSE_BUTTON = 'Brws';
static const uint32 MSG_DATABASE_NAME = 'DbNm';
static const uint32 MSG_ESTIMATE_BUTTON = 'Estm';
static const uint32 MSG_ESTIMATE_FILE_REFS = 'ERef';
static const uint32 MSG_IGNORE_CLASSIFICATION = 'IPCl';
static const uint32 MSG_PURGE_AGE = 'PuAg';
static const uint32 MSG_PURGE_BUTTON = 'Purg';
static const uint32 MSG_PURGE_POPULARITY = 'PuPo';
static const uint32 MSG_SERVER_MODE = 'SrvM';
void BrowseForDatabaseFile ();
void BrowseForFileToEstimate ();
void PollServerForChanges ();
BButton *m_AboutButtonPntr;
BButton *m_AddExampleButtonPntr;
BButton *m_BrowseButtonPntr;
BFilePanel *m_BrowseFilePanelPntr;
BButton *m_CreateDatabaseButtonPntr;
char m_DatabaseFileNameCachedValue [PATH_MAX];
BTextControl *m_DatabaseFileNameTextboxPntr;
bool m_DatabaseLoadDone;
BButton *m_EstimateSpamButtonPntr;
BFilePanel *m_EstimateSpamFilePanelPntr;
uint32 m_GenuineCountCachedValue;
BTextControl *m_GenuineCountTextboxPntr;
bool m_IgnorePreviousClassCachedValue;
BCheckBox *m_IgnorePreviousClassCheckboxPntr;
BButton *m_InstallThingsButtonPntr;
uint32 m_PurgeAgeCachedValue;
BTextControl *m_PurgeAgeTextboxPntr;
BButton *m_PurgeButtonPntr;
uint32 m_PurgePopularityCachedValue;
BTextControl *m_PurgePopularityTextboxPntr;
BButton *m_ResetToDefaultsButtonPntr;
ScoringModes m_ScoringModeCachedValue;
BMenuBar *m_ScoringModeMenuBarPntr;
BPopUpMenu *m_ScoringModePopUpMenuPntr;
bool m_ServerModeCachedValue;
BCheckBox *m_ServerModeCheckboxPntr;
uint32 m_SpamCountCachedValue;
BTextControl *m_SpamCountTextboxPntr;
bigtime_t m_TimeOfLastPoll;
TokenizeModes m_TokenizeModeCachedValue;
BMenuBar *m_TokenizeModeMenuBarPntr;
BPopUpMenu *m_TokenizeModePopUpMenuPntr;
uint32 m_WordCountCachedValue;
BTextControl *m_WordCountTextboxPntr;
};
static const uint32 MSG_LINE_DOWN = 'LnDn';
static const uint32 MSG_LINE_UP = 'LnUp';
static const uint32 MSG_PAGE_DOWN = 'PgDn';
static const uint32 MSG_PAGE_UP = 'PgUp';
class WordsView : public BView
{
public:
WordsView (BRect NewBounds);
virtual void AttachedToWindow ();
virtual void Draw (BRect UpdateRect);
virtual void KeyDown (const char *BufferPntr, int32 NumBytes);
virtual void MakeFocus (bool Focused);
virtual void MessageReceived (BMessage *MessagePntr);
virtual void MouseDown (BPoint point);
virtual void Pulse ();
private:
void MoveTextUpOrDown (uint32 MovementType);
void RefsDroppedHere (BMessage *MessagePntr);
BPictureButton *m_ArrowLineDownPntr;
BPictureButton *m_ArrowLineUpPntr;
BPictureButton *m_ArrowPageDownPntr;
BPictureButton *m_ArrowPageUpPntr;
float m_AscentHeight;
rgb_color m_BackgroundColour;
uint32 m_CachedTotalGenuineMessages;
uint32 m_CachedTotalSpamMessages;
uint32 m_CachedWordCount;
char m_FirstDisplayedWord [g_MaxWordLength + 1];
rgb_color m_FocusedColour;
bigtime_t m_LastTimeAKeyWasPressed;
float m_LineHeight;
BFont m_TextFont;
float m_TextHeight;
rgb_color m_UnfocusedColour;
};
class DatabaseWindow : public BWindow
{
public:
DatabaseWindow ();
virtual void MessageReceived (BMessage *MessagePntr);
virtual bool QuitRequested ();
private:
ControlsView *m_ControlsViewPntr;
WordsView *m_WordsViewPntr;
};
class ABSApp : public BApplication
{
public:
ABSApp ();
~ABSApp ();
virtual void AboutRequested ();
virtual void ArgvReceived (int32 argc, char **argv);
virtual status_t GetSupportedSuites (BMessage *MessagePntr);
virtual void MessageReceived (BMessage *MessagePntr);
virtual void Pulse ();
virtual bool QuitRequested ();
virtual void ReadyToRun ();
virtual void RefsReceived (BMessage *MessagePntr);
virtual BHandler *ResolveSpecifier (BMessage *MessagePntr, int32 Index,
BMessage *SpecifierMsgPntr, int32 SpecificationKind, const char *Property);
private:
status_t AddFileToDatabase (ClassificationTypes IsSpamOrWhat,
const char *FileName, char *ErrorMessage);
status_t AddPositionIOToDatabase (ClassificationTypes IsSpamOrWhat,
BPositionIO *MessageIOPntr, const char *OptionalFileName,
char *ErrorMessage);
status_t AddStringToDatabase (ClassificationTypes IsSpamOrWhat,
const char *String, char *ErrorMessage);
void AddWordsToSet (const char *InputString, size_t NumberOfBytes,
char PrefixCharacter, set<string> &WordSet);
status_t CreateDatabaseFile (char *ErrorMessage);
void DefaultSettings ();
status_t DeleteDatabaseFile (char *ErrorMessage);
status_t EvaluateFile (const char *PathName, BMessage *ReplyMessagePntr,
char *ErrorMessage);
status_t EvaluatePositionIO (BPositionIO *PositionIOPntr,
const char *OptionalFileName, BMessage *ReplyMessagePntr,
char *ErrorMessage);
status_t EvaluateString (const char *BufferPntr, ssize_t BufferSize,
BMessage *ReplyMessagePntr, char *ErrorMessage);
status_t GetWordsFromPositionIO (BPositionIO *PositionIOPntr,
const char *OptionalFileName, set<string> &WordSet, char *ErrorMessage);
status_t InstallThings (char *ErrorMessage);
status_t LoadDatabaseIfNeeded (char *ErrorMessage);
status_t LoadSaveDatabase (bool DoLoad, char *ErrorMessage);
public:
status_t LoadSaveSettings (bool DoLoad);
private:
status_t MakeBackup (char *ErrorMessage);
void MakeDatabaseEmpty ();
void ProcessScriptingMessage (BMessage *MessagePntr,
struct property_info *PropInfoPntr);
status_t PurgeOldWords (char *ErrorMessage);
status_t RecursivelyTokenizeMailComponent (
BMailComponent *ComponentPntr, const char *OptionalFileName,
set<string> &WordSet, char *ErrorMessage,
int RecursionLevel, int MaxRecursionLevel);
status_t SaveDatabaseIfNeeded (char *ErrorMessage);
status_t TokenizeParts (BPositionIO *PositionIOPntr,
const char *OptionalFileName, set<string> &WordSet, char *ErrorMessage);
status_t TokenizeWhole (BPositionIO *PositionIOPntr,
const char *OptionalFileName, set<string> &WordSet, char *ErrorMessage);
public:
bool m_DatabaseHasChanged;
BString m_DatabaseFileName;
bool m_IgnorePreviousClassification;
uint32 m_OldestAge;
uint32 m_PurgeAge;
uint32 m_PurgePopularity;
ScoringModes m_ScoringMode;
BPath m_SettingsDirectoryPath;
bool m_SettingsHaveChanged;
double m_SmallestUseableDouble;
TokenizeModes m_TokenizeMode;
uint32 m_TotalGenuineMessages;
uint32 m_TotalSpamMessages;
uint32 m_WordCount;
StatisticsMap m_WordMap;
};
static void
DisplayErrorMessage (
const char *MessageString = NULL,
int ErrorNumber = 0,
const char *TitleString = NULL)
{
BAlert *AlertPntr;
char ErrorBuffer [PATH_MAX + 1500];
if (TitleString == NULL)
TitleString = "SpamDBM Error Message";
if (MessageString == NULL)
{
if (ErrorNumber == 0)
MessageString = "No error, no message, why bother?";
else
MessageString = "Something went wrong";
}
if (ErrorNumber != 0)
{
sprintf (ErrorBuffer, "%s, error code $%X/%d (%s) has occured.",
MessageString, ErrorNumber, ErrorNumber, strerror (ErrorNumber));
MessageString = ErrorBuffer;
}
if (g_CommandLineMode || g_ServerMode)
cerr << TitleString << ": " << MessageString << endl;
else
{
AlertPntr = new BAlert (TitleString, MessageString,
"Acknowledge", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
if (AlertPntr != NULL) {
AlertPntr->SetFlags(AlertPntr->Flags() | B_CLOSE_ON_ESCAPE);
AlertPntr->Go ();
}
}
}
static void
WrapTextToStream (ostream& OutputStream, const char *TextPntr)
{
const int LineLength = 79;
char *StringPntr;
char TempString [LineLength+1];
TempString[LineLength] = 0;
while (*TextPntr != 0)
{
while (isspace (*TextPntr))
TextPntr++;
if (*TextPntr == 0)
break;
strncpy (TempString, TextPntr, LineLength);
StringPntr = TempString;
while (*StringPntr != 0)
StringPntr++;
if (StringPntr - TempString < LineLength)
{
OutputStream << TempString << endl;
TextPntr += StringPntr - TempString;
continue;
}
while (StringPntr > TempString)
{
if (isspace (*StringPntr))
break;
else
StringPntr--;
}
while (StringPntr > TempString && isspace (StringPntr[-1]))
StringPntr--;
if (StringPntr == TempString)
{
OutputStream << TempString << endl;
TextPntr += strlen (TempString);
continue;
}
*StringPntr = 0;
OutputStream << TempString << endl;
TextPntr += StringPntr - TempString;
}
}
ostream& PrintUsage (ostream& OutputStream);
ostream& PrintUsage (ostream& OutputStream)
{
struct property_info *PropInfoPntr;
OutputStream << "\nSpamDBM - A Spam Database Manager\n";
OutputStream << "Copyright © 2002 by Alexander G. M. Smith. ";
OutputStream << "Released to the public domain.\n\n";
WrapTextToStream (OutputStream, "Compiled on " __DATE__ " at " __TIME__
". $Id: spamdbm.cpp 30630 2009-05-05 01:31:01Z bga $ $HeadURL: http://svn.haiku-os.org/haiku/haiku/trunk/src/bin/mail_utils/spamdbm.cpp $");
OutputStream << "\n"
"This is a program for classifying e-mail messages as spam (junk mail which\n"
"you don't want to read) and regular genuine messages. It can learn what's\n"
"spam and what's genuine. You just give it a bunch of spam messages and a\n"
"bunch of non-spam ones. It uses them to make a list of the words from the\n"
"messages with the probability that each word is from a spam message or from\n"
"a genuine message. Later on, it can use those probabilities to classify\n"
"new messages as spam or not spam. If the classifier stops working well\n"
"(because the spammers have changed their writing style and vocabulary, or\n"
"your regular correspondants are writing like spammers), you can use this\n"
"program to update the list of words to identify the new messages\n"
"correctly.\n"
"\n"
"The original idea was from Paul Graham's algorithm, which has an excellent\n"
"writeup at: http://www.paulgraham.com/spam.html\n"
"\n"
"Gary Robinson came up with the improved algorithm, which you can read about at:\n"
"http://radio.weblogs.com/0101454/stories/2002/09/16/spamDetection.html\n"
"\n"
"Then he, Tim Peters and the SpamBayes mailing list developed the Chi-Squared\n"
"test, see http://mail.python.org/pipermail/spambayes/2002-October/001036.html\n"
"for one of the earlier messages leading from the central limit theorem to\n"
"the current chi-squared scoring method.\n"
"\n"
"Thanks go to Isaac Yonemoto for providing a better icon, which we can\n"
"unfortunately no longer use, since the Hormel company wants people to\n"
"avoid associating their meat product with junk e-mail.\n"
"\n"
"Tokenising code updated in 2005 to use some of the tricks that SpamBayes\n"
"uses to extract words from messages. In particular, HTML is now handled.\n"
"\n"
"Usage: Specify the operation as the first argument followed by more\n"
"information as appropriate. The program's configuration will affect the\n"
"actual operation (things like the name of the database file to use, or\n"
"whether it should allow non-email messages to be added). In command line\n"
"mode it will do the operation and exit. In GUI/server mode a command line\n"
"invocation will just send the command to the running server. You can also\n"
"use BeOS scripting (see the \"Hey\" command which you can get from\n"
"http://www.bebits.com/app/2042 ) to control the Spam server. And finally,\n"
"there's also a GUI interface which shows up if you start it without any\n"
"command line arguments.\n"
"\n"
"Commands:\n"
"\n"
"Quit\n"
"Stop the program. Useful if it's running as a server.\n"
"\n";
for (PropInfoPntr = g_ScriptingPropertyList + 0;
PropInfoPntr->name != 0;
PropInfoPntr++)
{
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
OutputStream << "Get " << PropInfoPntr->name << endl;
break;
case B_SET_PROPERTY:
OutputStream << "Set " << PropInfoPntr->name << " NewValue" << endl;
break;
case B_COUNT_PROPERTIES:
OutputStream << "Count " << PropInfoPntr->name << endl;
break;
case B_CREATE_PROPERTY:
OutputStream << "Create " << PropInfoPntr->name << endl;
break;
case B_DELETE_PROPERTY:
OutputStream << "Delete " << PropInfoPntr->name << endl;
break;
case B_EXECUTE_PROPERTY:
OutputStream << PropInfoPntr->name << endl;
break;
default:
OutputStream << "Buggy Command: " << PropInfoPntr->name << endl;
break;
}
WrapTextToStream (OutputStream, (char *)PropInfoPntr->usage);
OutputStream << endl;
}
return OutputStream;
}
static void
SubmitCommand (BMessage& CommandMessage)
{
status_t ErrorCode;
ErrorCode = be_app_messenger.SendMessage (&CommandMessage,
be_app_messenger ,
1000000 );
if (ErrorCode != B_OK)
cerr << "SubmitCommand failed to send a command, code " <<
ErrorCode << " (" << strerror (ErrorCode) << ")." << endl;
}
static void
SubmitCommandString (
PropertyNumbers Property,
uint32 CommandCode,
const char *StringArgument = NULL)
{
BMessage CommandMessage (CommandCode);
if (Property < 0 || Property >= PN_MAX)
{
DisplayErrorMessage ("SubmitCommandString bug.");
return;
}
CommandMessage.AddSpecifier (g_PropertyNames [Property]);
if (StringArgument != NULL)
CommandMessage.AddString (g_DataName, StringArgument);
SubmitCommand (CommandMessage);
}
static void
SubmitCommandInt32 (
PropertyNumbers Property,
uint32 CommandCode,
int32 Int32Argument)
{
BMessage CommandMessage (CommandCode);
if (Property < 0 || Property >= PN_MAX)
{
DisplayErrorMessage ("SubmitCommandInt32 bug.");
return;
}
CommandMessage.AddSpecifier (g_PropertyNames [Property]);
CommandMessage.AddInt32 (g_DataName, Int32Argument);
SubmitCommand (CommandMessage);
}
static void
SubmitCommandBool (
PropertyNumbers Property,
uint32 CommandCode,
bool BoolArgument)
{
BMessage CommandMessage (CommandCode);
if (Property < 0 || Property >= PN_MAX)
{
DisplayErrorMessage ("SubmitCommandBool bug.");
return;
}
CommandMessage.AddSpecifier (g_PropertyNames [Property]);
CommandMessage.AddBool (g_DataName, BoolArgument);
SubmitCommand (CommandMessage);
}
static void
EstimateRefFilesAndDisplay (BMessage *MessagePntr)
{
BAlert *AlertPntr;
BEntry Entry;
entry_ref EntryRef;
status_t ErrorCode;
int i, j;
BPath Path;
BMessage ReplyMessage;
BMessage ScriptingMessage;
const char *StringPntr;
float TempFloat;
int32 TempInt32;
char TempString [PATH_MAX + 1024 +
g_MaxInterestingWords * (g_MaxWordLength + 16)];
for (i = 0; MessagePntr->FindRef ("refs", i, &EntryRef) == B_OK; i++)
{
ErrorCode = Entry.SetTo (&EntryRef, true );
if (ErrorCode != B_OK || !Entry.Exists () || Entry.GetPath (&Path) != B_OK)
continue;
ScriptingMessage.MakeEmpty ();
ScriptingMessage.what = B_SET_PROPERTY;
ScriptingMessage.AddSpecifier (g_PropertyNames[PN_EVALUATE]);
ScriptingMessage.AddString (g_DataName, Path.Path ());
if (be_app_messenger.SendMessage (&ScriptingMessage,&ReplyMessage) != B_OK)
break;
if (ReplyMessage.FindInt32 ("error", &TempInt32) != B_OK ||
TempInt32 != B_OK)
break;
ReplyMessage.FindFloat (g_ResultName, &TempFloat);
sprintf (TempString, "%f spam ratio for \"%s\".\nThe top words are:",
(double) TempFloat, Path.Path ());
for (j = 0; j < 20 ; j++)
{
if (ReplyMessage.FindString ("words", j, &StringPntr) != B_OK ||
ReplyMessage.FindFloat ("ratios", j, &TempFloat) != B_OK)
break;
sprintf (TempString + strlen (TempString), "\n%s / %f",
StringPntr, TempFloat);
}
if (j >= 20 && j < g_MaxInterestingWords)
sprintf (TempString + strlen (TempString), "\nAnd up to %d more words.",
g_MaxInterestingWords - j);
AlertPntr = new BAlert ("Estimate", TempString, "OK");
if (AlertPntr != NULL) {
AlertPntr->SetFlags(AlertPntr->Flags() | B_CLOSE_ON_ESCAPE);
AlertPntr->Go ();
}
}
}
static double ChiSquaredProbability (double x2, int v)
{
int halfV = v / 2;
int i;
double m;
double sum;
double term;
if (v & 1)
return -1.0;
m = x2 / 2.0;
sum = term = exp (-m);
for (i = 1; i < halfV; i++)
{
term *= m / i;
sum += term;
}
if (sum > 1.0)
return 1.0;
return sum;
}
static status_t RemoveSpamPrefixFromSubjectAttribute (BNode *BNodePntr)
{
status_t ErrorCode;
const char *MailSubjectName = "MAIL:subject";
char *StringPntr;
char SubjectString [2000];
ErrorCode = BNodePntr->ReadAttr (MailSubjectName,
B_STRING_TYPE, 0 , SubjectString,
sizeof (SubjectString) - 1);
if (ErrorCode <= 0)
return 0;
if (ErrorCode >= (int) sizeof (SubjectString) - 1)
return 0;
SubjectString [ErrorCode] = 0;
ErrorCode = 0;
if (strncmp (SubjectString, "[Spam ", 6) == 0)
{
for (StringPntr = SubjectString;
*StringPntr != 0 && *StringPntr != ']'; StringPntr++)
;
if (StringPntr[0] == ']' && StringPntr[1] == ' ')
{
ErrorCode = BNodePntr->RemoveAttr (MailSubjectName);
ErrorCode = BNodePntr->WriteAttr (MailSubjectName,
B_STRING_TYPE, 0 ,
StringPntr + 2, strlen (StringPntr + 2) + 1);
if (ErrorCode > 0)
ErrorCode = 0;
}
}
return ErrorCode;
}
static size_t TokenizerPassLowerCase (
char *BufferPntr,
size_t NumberOfBytes)
{
char *EndOfStringPntr;
EndOfStringPntr = BufferPntr + NumberOfBytes;
while (BufferPntr < EndOfStringPntr)
{
if (*BufferPntr >= 'A' && *BufferPntr <= 'Z')
*BufferPntr = *BufferPntr + ('a' - 'A');
BufferPntr++;
}
return NumberOfBytes;
}
static void
AddWordAndPrefixToSet (
string &Word,
const char *PrefixString,
set<string> &WordSet)
{
if (Word.empty ())
return;
if (Word.size () > g_MaxWordLength)
Word.resize (g_MaxWordLength);
Word.insert (0, PrefixString);
WordSet.insert (Word);
}
static size_t TokenizerPassExtractURLs (
char *BufferPntr,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet)
{
char *AtSignStringPntr;
char *HostStringPntr;
char *InputStringEndPntr;
char *InputStringPntr;
char *OptionsStringPntr;
char *PathStringPntr;
char PrefixString [2];
char *ProtocolStringPntr;
string Word;
InputStringPntr = BufferPntr;
InputStringEndPntr = BufferPntr + NumberOfBytes;
PrefixString [0] = PrefixCharacter;
PrefixString [1] = 0;
while (InputStringPntr < InputStringEndPntr - 4)
{
HostStringPntr = NULL;
if (memcmp (InputStringPntr, "www.", 4) == 0)
HostStringPntr = InputStringPntr;
else if (memcmp (InputStringPntr, "://", 3) == 0)
{
ProtocolStringPntr = InputStringPntr;
while (ProtocolStringPntr > BufferPntr &&
isalpha (ProtocolStringPntr[-1]))
ProtocolStringPntr--;
Word.assign (ProtocolStringPntr,
(InputStringPntr - ProtocolStringPntr) + 1 );
AddWordAndPrefixToSet (Word, PrefixString, WordSet);
HostStringPntr = InputStringPntr + 3;
}
if (HostStringPntr == NULL)
{
InputStringPntr++;
continue;
}
InputStringPntr = HostStringPntr;
AtSignStringPntr = NULL;
while (InputStringPntr < InputStringEndPntr &&
(*InputStringPntr != '/' && !isspace (*InputStringPntr)))
{
if (*InputStringPntr == '@')
AtSignStringPntr = InputStringPntr;
InputStringPntr++;
}
if (AtSignStringPntr != NULL)
{
Word.assign (HostStringPntr,
AtSignStringPntr - HostStringPntr + 1 );
AddWordAndPrefixToSet (Word, PrefixString, WordSet);
HostStringPntr = AtSignStringPntr + 1;
}
Word.assign (HostStringPntr, InputStringPntr - HostStringPntr);
AddWordAndPrefixToSet (Word, PrefixString, WordSet);
PathStringPntr = InputStringPntr;
OptionsStringPntr = NULL;
while (InputStringPntr < InputStringEndPntr &&
(*InputStringPntr != '"' && !isspace (*InputStringPntr)))
{
if (OptionsStringPntr == NULL &&
(*InputStringPntr == '?' || *InputStringPntr == '#'))
OptionsStringPntr = InputStringPntr;
InputStringPntr++;
}
if (OptionsStringPntr == NULL)
{
Word.assign (PathStringPntr, InputStringPntr - PathStringPntr);
AddWordAndPrefixToSet (Word, PrefixString, WordSet);
}
else
{
Word.assign (PathStringPntr, OptionsStringPntr - PathStringPntr);
AddWordAndPrefixToSet (Word, PrefixString, WordSet);
Word.assign (OptionsStringPntr, InputStringPntr - OptionsStringPntr);
AddWordAndPrefixToSet (Word, PrefixString, WordSet);
}
}
return NumberOfBytes;
}
static size_t TokenizerPassTruncateLongAsianWords (
char *BufferPntr,
size_t NumberOfBytes)
{
char *EndOfStringPntr;
char *InputStringPntr;
int Letter;
char *OutputStringPntr;
char *StartOfInputLongUnicodeWord;
char *StartOfOutputLongUnicodeWord;
InputStringPntr = BufferPntr;
EndOfStringPntr = InputStringPntr + NumberOfBytes;
OutputStringPntr = InputStringPntr;
StartOfInputLongUnicodeWord = NULL;
StartOfOutputLongUnicodeWord = NULL;
while (InputStringPntr < EndOfStringPntr)
{
Letter = (unsigned char) *InputStringPntr;
if (Letter < 128)
{
if (StartOfInputLongUnicodeWord != NULL)
{
if (InputStringPntr - StartOfInputLongUnicodeWord >
(int) g_MaxWordLength * 2)
{
OutputStringPntr = StartOfOutputLongUnicodeWord + 1;
while (OutputStringPntr < InputStringPntr)
{
Letter = (unsigned char) *OutputStringPntr;
if (Letter < 128 || Letter >= 192)
break;
++OutputStringPntr;
}
}
StartOfInputLongUnicodeWord = NULL;
}
}
else if (Letter >= 192 && StartOfInputLongUnicodeWord == NULL)
{
StartOfInputLongUnicodeWord = InputStringPntr;
StartOfOutputLongUnicodeWord = OutputStringPntr;
}
*OutputStringPntr++ = *InputStringPntr++;
}
return OutputStringPntr - BufferPntr;
}
static size_t TokenizerPassGetPlainWords (
char *BufferPntr,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet)
{
string AccumulatedWord;
char *EndOfStringPntr;
size_t Length;
int Letter;
if (NumberOfBytes <= 0)
return 0;
if (PrefixCharacter != 0)
AccumulatedWord = PrefixCharacter;
EndOfStringPntr = BufferPntr + NumberOfBytes;
while (true)
{
if (BufferPntr >= EndOfStringPntr)
Letter = EOF;
else
Letter = (unsigned char) *BufferPntr++;
if (Letter < 0 ||
(Letter < 128 && g_SpaceCharacters[Letter]))
{
while ((Length = AccumulatedWord.size()) > 0 &&
AccumulatedWord [Length-1] == '.')
AccumulatedWord.resize (Length - 1);
if (PrefixCharacter != 0)
Length--;
if (Length > 0 && Length <= g_MaxWordLength)
WordSet.insert (AccumulatedWord);
if (PrefixCharacter != 0)
AccumulatedWord = PrefixCharacter;
else
AccumulatedWord.resize (0);
}
else
AccumulatedWord.append (1 , (char) Letter);
if (Letter < 0)
break;
}
return NumberOfBytes;
}
static size_t TokenizerUtilRemoveStartEndThing (
char *BufferPntr,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet,
const char *ThingStartCode,
const char *ThingEndCode,
bool ReplaceWithSpace)
{
char *EndOfStringPntr;
bool FoundAndDeletedThing;
char *InputStringPntr;
char *OutputStringPntr;
int ThingEndLength;
char *ThingEndPntr;
int ThingStartLength;
InputStringPntr = BufferPntr;
EndOfStringPntr = InputStringPntr + NumberOfBytes;
OutputStringPntr = InputStringPntr;
ThingStartLength = strlen (ThingStartCode);
ThingEndLength = strlen (ThingEndCode);
if (ThingStartLength <= 0)
return NumberOfBytes;
while (InputStringPntr < EndOfStringPntr)
{
FoundAndDeletedThing = false;
if (EndOfStringPntr - InputStringPntr >=
ThingStartLength + ThingEndLength &&
*InputStringPntr == *ThingStartCode &&
memcmp (InputStringPntr, ThingStartCode, ThingStartLength) == 0)
{
ThingEndPntr = InputStringPntr + ThingStartLength;
while (EndOfStringPntr - ThingEndPntr >= ThingEndLength)
{
if (ThingEndLength == 0 ||
(*ThingEndPntr == *ThingEndCode &&
memcmp (ThingEndPntr, ThingEndCode, ThingEndLength) == 0))
{
TokenizerPassGetPlainWords (InputStringPntr + ThingStartLength,
ThingEndPntr - (InputStringPntr + ThingStartLength),
PrefixCharacter, WordSet);
InputStringPntr = ThingEndPntr + ThingEndLength;
if (ReplaceWithSpace)
*OutputStringPntr++ = ' ';
FoundAndDeletedThing = true;
break;
}
ThingEndPntr++;
}
}
if (!FoundAndDeletedThing)
*OutputStringPntr++ = *InputStringPntr++;
}
return OutputStringPntr - BufferPntr;
}
static size_t TokenizerPassRemoveHTMLComments (
char *BufferPntr,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet)
{
return TokenizerUtilRemoveStartEndThing (BufferPntr, NumberOfBytes,
PrefixCharacter, WordSet, "<!--", "-->", false);
}
static size_t TokenizerPassRemoveHTMLStyle (
char *BufferPntr,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet)
{
return TokenizerUtilRemoveStartEndThing (BufferPntr, NumberOfBytes,
PrefixCharacter, WordSet,
"<style", "/style>", false );
}
static size_t TokenizerPassJapanesePeriodsToSpaces (
char *BufferPntr,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet)
{
size_t BytesRemaining = NumberOfBytes;
BytesRemaining = TokenizerUtilRemoveStartEndThing (BufferPntr,
BytesRemaining, PrefixCharacter, WordSet, "。" , "", true);
BytesRemaining = TokenizerUtilRemoveStartEndThing (BufferPntr,
BytesRemaining, PrefixCharacter, WordSet, "、" , "", true);
return BytesRemaining;
}
static size_t TokenizerPassRemoveHTMLTags (
char *BufferPntr,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet)
{
size_t BytesRemaining = NumberOfBytes;
BytesRemaining = TokenizerUtilRemoveStartEndThing (BufferPntr,
BytesRemaining, PrefixCharacter, WordSet, " ", "", true);
BytesRemaining = TokenizerUtilRemoveStartEndThing (BufferPntr,
BytesRemaining, PrefixCharacter, WordSet, "<p", ">", true);
BytesRemaining = TokenizerUtilRemoveStartEndThing (BufferPntr,
BytesRemaining, PrefixCharacter, WordSet, "<br", ">", true);
BytesRemaining = TokenizerUtilRemoveStartEndThing (BufferPntr,
BytesRemaining, PrefixCharacter, WordSet, "<", ">", false);
return BytesRemaining;
}
ABSApp::ABSApp ()
: BApplication (g_ABSAppSignature),
m_DatabaseHasChanged (false),
m_SettingsHaveChanged (false)
{
status_t ErrorCode;
int HalvingCount;
int i;
const void *ResourceData;
size_t ResourceSize;
BResources *ResourcesPntr;
MakeDatabaseEmpty ();
ErrorCode =
find_directory (B_USER_SETTINGS_DIRECTORY, &m_SettingsDirectoryPath);
if (ErrorCode == B_OK)
ErrorCode = m_SettingsDirectoryPath.Append (g_SettingsDirectoryName);
if (ErrorCode != B_OK)
m_SettingsDirectoryPath.SetTo (".");
memset (g_SpaceCharacters, 1, sizeof (g_SpaceCharacters));
g_SpaceCharacters['\''] = false;
g_SpaceCharacters['-'] = false;
g_SpaceCharacters['$'] = false;
g_SpaceCharacters['.'] = false;
for (i = '0'; i <= '9'; i++)
g_SpaceCharacters[i] = false;
for (i = 'A'; i <= 'Z'; i++)
g_SpaceCharacters[i] = false;
for (i = 'a'; i <= 'z'; i++)
g_SpaceCharacters[i] = false;
if ((ResourcesPntr = AppResources ()) != NULL && (ResourceData =
ResourcesPntr->LoadResource ('CURS', "Busy Cursor", &ResourceSize)) != NULL
&& ResourceSize >= 68 )
g_BusyCursor = new BCursor (ResourceData);
m_SmallestUseableDouble = 1.0;
HalvingCount = 0;
while (HalvingCount < 10000 && m_SmallestUseableDouble > 0.0)
{
HalvingCount++;
m_SmallestUseableDouble /= 2;
}
HalvingCount -= 50 + sizeof (double) * 8;
m_SmallestUseableDouble = 1.0;
while (HalvingCount > 0)
{
HalvingCount--;
m_SmallestUseableDouble /= 2;
}
}
ABSApp::~ABSApp ()
{
status_t ErrorCode;
char ErrorMessage [PATH_MAX + 1024];
if (m_SettingsHaveChanged)
LoadSaveSettings (false );
if ((ErrorCode = SaveDatabaseIfNeeded (ErrorMessage)) != B_OK)
DisplayErrorMessage (ErrorMessage, ErrorCode, "Exiting Error");
delete g_BusyCursor;
g_BusyCursor = NULL;
}
void
ABSApp::AboutRequested ()
{
BAlert *AboutAlertPntr;
AboutAlertPntr = new BAlert ("About",
"SpamDBM - Spam Database Manager\n\n"
"This is a BeOS program for classifying e-mail messages as spam (unwanted \
junk mail) or as genuine mail using a Bayesian statistical approach. There \
is also a Mail Daemon Replacement add-on to filter mail using the \
classification statistics collected earlier.\n\n"
"Written by Alexander G. M. Smith, fall 2002.\n\n"
"The original idea was from Paul Graham's algorithm, which has an excellent \
writeup at: http://www.paulgraham.com/spam.html\n\n"
"Gary Robinson came up with the improved algorithm, which you can read about \
at: http://radio.weblogs.com/0101454/stories/2002/09/16/spamDetection.html\n\n"
"Mr. Robinson, Tim Peters and the SpamBayes mailing list people then \
developed the even better chi-squared scoring method.\n\n"
"Icon courtesy of Isaac Yonemoto, though it is no longer used since Hormel \
doesn't want their meat product associated with junk e-mail.\n\n"
"Tokenising code updated in 2005 to use some of the tricks that SpamBayes \
uses to extract words from messages. In particular, HTML is now handled.\n\n"
"Released to the public domain, with no warranty.\n"
"$Revision: 30630 $\n"
"Compiled on " __DATE__ " at " __TIME__ ".", "Done");
if (AboutAlertPntr != NULL)
{
AboutAlertPntr->SetFlags(AboutAlertPntr->Flags() | B_CLOSE_ON_ESCAPE);
AboutAlertPntr->Go ();
}
}
status_t ABSApp::AddFileToDatabase (
ClassificationTypes IsSpamOrWhat,
const char *FileName,
char *ErrorMessage)
{
status_t ErrorCode;
BFile MessageFile;
BMessage TempBMessage;
ErrorCode = MessageFile.SetTo (FileName, B_READ_ONLY);
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "Unable to open file \"%s\" for reading", FileName);
return ErrorCode;
}
ErrorCode = AddPositionIOToDatabase (IsSpamOrWhat,
&MessageFile, FileName, ErrorMessage);
MessageFile.Unset ();
if (ErrorCode != B_OK)
return ErrorCode;
return EvaluateFile (FileName, &TempBMessage, ErrorMessage);
}
status_t ABSApp::AddPositionIOToDatabase (
ClassificationTypes IsSpamOrWhat,
BPositionIO *MessageIOPntr,
const char *OptionalFileName,
char *ErrorMessage)
{
BNode *BNodePntr;
char ClassificationString [NAME_MAX];
StatisticsMap::iterator DataIter;
status_t ErrorCode = 0;
pair<StatisticsMap::iterator,bool> InsertResult;
uint32 NewAge;
StatisticsRecord NewStatistics;
ClassificationTypes PreviousClassification;
StatisticsPointer StatisticsPntr;
set<string>::iterator WordEndIter;
set<string>::iterator WordIter;
set<string> WordSet;
NewAge = m_TotalGenuineMessages + m_TotalSpamMessages;
if (NewAge >= 0xFFFFFFF0UL)
{
sprintf (ErrorMessage,
"The database is full! There are %" B_PRIu32 " messages in "
"it and we can't add any more without overflowing the maximum integer "
"representation in 32 bits", NewAge);
return B_NO_MEMORY;
}
PreviousClassification = CL_UNCERTAIN;
BNodePntr = dynamic_cast<BNode *> (MessageIOPntr);
if (BNodePntr != NULL)
{
ErrorCode = BNodePntr->ReadAttr (g_AttributeNameClassification,
B_STRING_TYPE, 0 , ClassificationString,
sizeof (ClassificationString) - 1);
if (ErrorCode <= 0)
strcpy (ClassificationString, "none");
else
ClassificationString [ErrorCode] = 0;
if (strcasecmp (ClassificationString, g_ClassifiedSpam) == 0)
PreviousClassification = CL_SPAM;
else if (strcasecmp (ClassificationString, g_ClassifiedGenuine) == 0)
PreviousClassification = CL_GENUINE;
}
if (!m_IgnorePreviousClassification &&
PreviousClassification != CL_UNCERTAIN)
{
if (IsSpamOrWhat == PreviousClassification)
{
sprintf (ErrorMessage, "Ignoring file \"%s\" since it seems to have "
"already been classified as %s.", OptionalFileName,
g_ClassificationTypeNames [IsSpamOrWhat]);
}
else
{
sprintf (ErrorMessage, "Changing existing classification of file \"%s\" "
"from %s to %s.", OptionalFileName,
g_ClassificationTypeNames [PreviousClassification],
g_ClassificationTypeNames [IsSpamOrWhat]);
}
DisplayErrorMessage (ErrorMessage, 0, "Note");
}
if (!m_IgnorePreviousClassification &&
IsSpamOrWhat == PreviousClassification)
return B_OK;
ErrorCode = GetWordsFromPositionIO (MessageIOPntr, OptionalFileName,
WordSet, ErrorMessage);
if (ErrorCode != B_OK)
return ErrorCode;
m_DatabaseHasChanged = true;
if (!m_IgnorePreviousClassification &&
PreviousClassification == CL_SPAM && m_TotalSpamMessages > 0)
m_TotalSpamMessages--;
if (IsSpamOrWhat == CL_SPAM)
m_TotalSpamMessages++;
if (!m_IgnorePreviousClassification &&
PreviousClassification == CL_GENUINE && m_TotalGenuineMessages > 0)
m_TotalGenuineMessages--;
if (IsSpamOrWhat == CL_GENUINE)
m_TotalGenuineMessages++;
if (BNodePntr != NULL)
{
ErrorCode = BNodePntr->RemoveAttr (g_AttributeNameClassification);
if (IsSpamOrWhat != CL_UNCERTAIN)
{
strcpy (ClassificationString, g_ClassificationTypeNames [IsSpamOrWhat]);
ErrorCode = BNodePntr->WriteAttr (g_AttributeNameClassification,
B_STRING_TYPE, 0 ,
ClassificationString, strlen (ClassificationString) + 1);
}
}
WordEndIter = WordSet.end ();
for (WordIter = WordSet.begin (); WordIter != WordEndIter; WordIter++)
{
if ((DataIter = m_WordMap.find (*WordIter)) == m_WordMap.end ())
{
if (IsSpamOrWhat == CL_UNCERTAIN)
continue;
memset (&NewStatistics, 0, sizeof (NewStatistics));
InsertResult = m_WordMap.insert (
StatisticsMap::value_type (*WordIter, NewStatistics));
if (!InsertResult.second)
{
sprintf (ErrorMessage, "Failed to insert new database entry for "
"word \"%s\", while processing file \"%s\"",
WordIter->c_str (), OptionalFileName);
return B_NO_MEMORY;
}
DataIter = InsertResult.first;
m_WordCount++;
}
StatisticsPntr = &DataIter->second;
StatisticsPntr->age = NewAge;
if (IsSpamOrWhat == CL_SPAM)
StatisticsPntr->spamCount++;
if (IsSpamOrWhat == CL_GENUINE)
StatisticsPntr->genuineCount++;
if (!m_IgnorePreviousClassification &&
PreviousClassification == CL_SPAM && StatisticsPntr->spamCount > 0)
StatisticsPntr->spamCount--;
if (!m_IgnorePreviousClassification &&
PreviousClassification == CL_GENUINE && StatisticsPntr->genuineCount > 0)
StatisticsPntr->genuineCount--;
}
return B_OK;
}
status_t ABSApp::AddStringToDatabase (
ClassificationTypes IsSpamOrWhat,
const char *String,
char *ErrorMessage)
{
BMemoryIO MemoryIO (String, strlen (String));
return AddPositionIOToDatabase (IsSpamOrWhat, &MemoryIO,
"Memory Buffer" , ErrorMessage);
}
void
ABSApp::AddWordsToSet (
const char *InputString,
size_t NumberOfBytes,
char PrefixCharacter,
set<string> &WordSet)
{
char *BufferPntr;
size_t CurrentSize;
int PassNumber;
BufferPntr = new char [NumberOfBytes];
if (BufferPntr == NULL)
return;
memcpy (BufferPntr, InputString, NumberOfBytes);
CurrentSize = NumberOfBytes;
for (PassNumber = 1; PassNumber <= 8 && CurrentSize > 0 ; PassNumber++)
{
switch (PassNumber)
{
case 1:
CurrentSize = TokenizerPassLowerCase (BufferPntr, CurrentSize);
break;
case 2: CurrentSize = TokenizerPassJapanesePeriodsToSpaces (
BufferPntr, CurrentSize, PrefixCharacter, WordSet); break;
case 3: CurrentSize = TokenizerPassTruncateLongAsianWords (
BufferPntr, CurrentSize); break;
case 4: CurrentSize = TokenizerPassRemoveHTMLComments (
BufferPntr, CurrentSize, 'Z', WordSet); break;
case 5: CurrentSize = TokenizerPassRemoveHTMLStyle (
BufferPntr, CurrentSize, 'Z', WordSet); break;
case 6: CurrentSize = TokenizerPassExtractURLs (
BufferPntr, CurrentSize, 'Z', WordSet); break;
case 7: CurrentSize = TokenizerPassRemoveHTMLTags (
BufferPntr, CurrentSize, 'Z', WordSet); break;
case 8: CurrentSize = TokenizerPassGetPlainWords (
BufferPntr, CurrentSize, PrefixCharacter, WordSet); break;
default: break;
}
}
delete [] BufferPntr;
}
void
ABSApp::ArgvReceived (int32 argc, char **argv)
{
if (g_CommanderLooperPntr != NULL)
g_CommanderLooperPntr->CommandArguments (argc, argv);
}
status_t ABSApp::CreateDatabaseFile (char *ErrorMessage)
{
MakeDatabaseEmpty ();
m_DatabaseHasChanged = true;
return SaveDatabaseIfNeeded (ErrorMessage);
}
void
ABSApp::DefaultSettings ()
{
status_t ErrorCode;
BPath DatabasePath (m_SettingsDirectoryPath);
char TempString [PATH_MAX];
ErrorCode = DatabasePath.Append (g_DefaultDatabaseFileName);
if (ErrorCode != B_OK)
strcpy (TempString, g_DefaultDatabaseFileName);
else
strcpy (TempString, DatabasePath.Path ());
m_DatabaseFileName.SetTo (TempString);
m_IgnorePreviousClassification = true;
g_ServerMode = true;
m_PurgeAge = 2000;
m_PurgePopularity = 2;
m_ScoringMode = SM_CHISQUARED;
m_TokenizeMode = TM_ANY_TEXT_HEADER;
m_SettingsHaveChanged = true;
}
status_t ABSApp::DeleteDatabaseFile (char *ErrorMessage)
{
BEntry FileEntry;
status_t ErrorCode;
int i;
char TempString [PATH_MAX+20];
MakeDatabaseEmpty ();
m_DatabaseHasChanged = false;
for (i = 0; i < g_MaxBackups; i++)
{
strcpy (TempString, m_DatabaseFileName.String ());
sprintf (TempString + strlen (TempString), g_BackupSuffix, i);
ErrorCode = FileEntry.SetTo (TempString);
if (ErrorCode == B_OK)
FileEntry.Remove ();
}
strcpy (TempString, m_DatabaseFileName.String ());
ErrorCode = FileEntry.SetTo (TempString);
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "While deleting, failed to make BEntry for "
"\"%s\" (does the directory exist?)", TempString);
return ErrorCode;
}
ErrorCode = FileEntry.Remove ();
if (ErrorCode != B_OK)
sprintf (ErrorMessage, "While deleting, failed to remove file "
"\"%s\"", TempString);
return ErrorCode;
}
status_t ABSApp::EvaluateFile (
const char *PathName,
BMessage *ReplyMessagePntr,
char *ErrorMessage)
{
status_t ErrorCode;
float TempFloat;
BFile TextFile;
ErrorCode = TextFile.SetTo (PathName, B_READ_ONLY);
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "Problems opening file \"%s\" for evaluating",
PathName);
return ErrorCode;
}
ErrorCode =
EvaluatePositionIO (&TextFile, PathName, ReplyMessagePntr, ErrorMessage);
if (ErrorCode == B_OK &&
ReplyMessagePntr->FindFloat (g_ResultName, &TempFloat) == B_OK)
{
TextFile.WriteAttr (g_AttributeNameSpamRatio, B_FLOAT_TYPE,
0 , &TempFloat, sizeof (TempFloat));
RemoveSpamPrefixFromSubjectAttribute (&TextFile);
}
return ErrorCode;
}
struct WordAndRatioStruct
{
double probabilityRatio;
const string *wordPntr;
bool operator() (
const WordAndRatioStruct &ItemA,
const WordAndRatioStruct &ItemB) const
{
return
(fabs (ItemA.probabilityRatio - 0.5) <
fabs (ItemB.probabilityRatio - 0.5));
};
};
status_t ABSApp::EvaluatePositionIO (
BPositionIO *PositionIOPntr,
const char *OptionalFileName,
BMessage *ReplyMessagePntr,
char *ErrorMessage)
{
StatisticsMap::iterator DataEndIter;
StatisticsMap::iterator DataIter;
status_t ErrorCode;
double GenuineProbability;
uint32 GenuineSpamSum;
int i;
priority_queue<
WordAndRatioStruct ,
vector<WordAndRatioStruct> ,
WordAndRatioStruct >
PriorityQueue;
double ProductGenuine;
double ProductLogGenuine;
double ProductLogSpam;
double ProductSpam;
double RawProbabilityRatio;
float ResultRatio;
double SpamProbability;
StatisticsPointer StatisticsPntr;
double TempDouble;
double TotalGenuine;
double TotalSpam;
WordAndRatioStruct WordAndRatio;
set<string>::iterator WordEndIter;
set<string>::iterator WordIter;
const WordAndRatioStruct *WordRatioPntr;
set<string> WordSet;
ErrorCode = GetWordsFromPositionIO (PositionIOPntr, OptionalFileName,
WordSet, ErrorMessage);
if (ErrorCode != B_OK)
return ErrorCode;
if (m_TotalGenuineMessages <= 0)
TotalGenuine = 1.0;
else
TotalGenuine = m_TotalGenuineMessages;
if (m_TotalSpamMessages <= 0)
TotalSpam = 1.0;
else
TotalSpam = m_TotalSpamMessages;
WordEndIter = WordSet.end ();
DataEndIter = m_WordMap.end ();
for (WordIter = WordSet.begin (); WordIter != WordEndIter; WordIter++)
{
WordAndRatio.wordPntr = &(*WordIter);
if ((DataIter = m_WordMap.find (*WordIter)) != DataEndIter)
{
StatisticsPntr = &DataIter->second;
SpamProbability = StatisticsPntr->spamCount / TotalSpam;
GenuineProbability = StatisticsPntr->genuineCount / TotalGenuine;
if (SpamProbability + GenuineProbability > 0)
RawProbabilityRatio =
SpamProbability / (SpamProbability + GenuineProbability);
else
RawProbabilityRatio = 0.5;
GenuineSpamSum =
StatisticsPntr->spamCount + StatisticsPntr->genuineCount;
WordAndRatio.probabilityRatio =
(g_RobinsonS * g_RobinsonX + GenuineSpamSum * RawProbabilityRatio) /
(g_RobinsonS + GenuineSpamSum);
}
else
WordAndRatio.probabilityRatio = g_RobinsonX;
PriorityQueue.push (WordAndRatio);
}
ProductGenuine = 1.0;
ProductLogGenuine = 0.0;
ProductSpam = 1.0;
ProductLogSpam = 0.0;
for (i = 0;
i < g_MaxInterestingWords && !PriorityQueue.empty();
i++, PriorityQueue.pop())
{
WordRatioPntr = &PriorityQueue.top();
ProductSpam *= WordRatioPntr->probabilityRatio;
ProductGenuine *= 1.0 - WordRatioPntr->probabilityRatio;
if (ProductSpam < m_SmallestUseableDouble)
{
ProductLogSpam += log (ProductSpam);
ProductSpam = 1.0;
}
if (ProductGenuine < m_SmallestUseableDouble)
{
ProductLogGenuine += log (ProductGenuine);
ProductGenuine = 1.0;
}
ReplyMessagePntr->AddString ("words", WordRatioPntr->wordPntr->c_str ());
ReplyMessagePntr->AddFloat ("ratios", WordRatioPntr->probabilityRatio);
}
if (i > 0)
{
ProductLogSpam += log (ProductSpam);
ProductLogGenuine += log (ProductGenuine);
}
if (m_ScoringMode == SM_ROBINSON)
{
if (i > 0)
{
ProductSpam = exp (ProductLogSpam / i);
ProductGenuine = exp (ProductLogGenuine / i);
ResultRatio = ProductSpam / (ProductGenuine + ProductSpam);
}
else
ResultRatio = g_RobinsonX;
}
else if (m_ScoringMode == SM_CHISQUARED)
{
TempDouble = ProductLogSpam;
ProductLogSpam = ProductLogGenuine;
ProductLogGenuine = TempDouble;
if (i > 0)
{
ProductSpam =
1.0 - ChiSquaredProbability (-2.0 * ProductLogSpam, 2 * i);
ProductGenuine =
1.0 - ChiSquaredProbability (-2.0 * ProductLogGenuine, 2 * i);
ResultRatio = (ProductSpam - ProductGenuine + 1.0) / 2.0;
}
else
ResultRatio = 0.5;
}
else
{
strcpy (ErrorMessage, "Unknown scoring mode specified in settings");
return B_BAD_VALUE;
}
ReplyMessagePntr->AddFloat (g_ResultName, ResultRatio);
return B_OK;
}
status_t ABSApp::EvaluateString (
const char *BufferPntr,
ssize_t BufferSize,
BMessage *ReplyMessagePntr,
char *ErrorMessage)
{
BMemoryIO MemoryIO (BufferPntr, BufferSize);
return EvaluatePositionIO (&MemoryIO, "Memory Buffer",
ReplyMessagePntr, ErrorMessage);
}
status_t ABSApp::GetSupportedSuites (BMessage *MessagePntr)
{
BPropertyInfo TempPropInfo (g_ScriptingPropertyList);
MessagePntr->AddString ("suites", "suite/x-vnd.agmsmith.spamdbm");
MessagePntr->AddFlat ("messages", &TempPropInfo);
return BApplication::GetSupportedSuites (MessagePntr);
}
status_t ABSApp::GetWordsFromPositionIO (
BPositionIO *PositionIOPntr,
const char *OptionalFileName,
set<string> &WordSet,
char *ErrorMessage)
{
status_t ErrorCode;
if (m_TokenizeMode == TM_WHOLE)
ErrorCode = TokenizeWhole (PositionIOPntr, OptionalFileName,
WordSet, ErrorMessage);
else
ErrorCode = TokenizeParts (PositionIOPntr, OptionalFileName,
WordSet, ErrorMessage);
if (ErrorCode == B_OK && WordSet.empty ())
{
sprintf (ErrorMessage, "No words were found in \"%s\"", OptionalFileName);
ErrorCode = ENOMSG;
}
return ErrorCode;
}
status_t ABSApp::InstallThings (char *ErrorMessage)
{
int32 Cookie;
dev_t DeviceID;
status_t ErrorCode = B_OK;
fs_info FSInfo;
int32 i;
int32 iClassification;
int32 iProbability;
int32 j;
index_info IndexInfo;
BMimeType MimeType;
BMessage Parameters;
const char *StringPntr;
bool TempBool;
int32 TempInt32;
Cookie = 0;
while ((DeviceID = next_dev (&Cookie)) >= 0)
{
if (!fs_stat_dev (DeviceID, &FSInfo) && (FSInfo.flags & B_FS_HAS_QUERY))
{
if (fs_stat_index (DeviceID, g_AttributeNameClassification, &IndexInfo)
&& errno == B_ENTRY_NOT_FOUND)
{
if (fs_create_index (DeviceID, g_AttributeNameClassification,
B_STRING_TYPE, 0 ))
{
ErrorCode = errno;
sprintf (ErrorMessage, "Unable to make string index %s on "
"volume #%d, volume name \"%s\", file system type \"%s\", "
"on device \"%s\"", g_AttributeNameClassification,
(int) DeviceID, FSInfo.volume_name, FSInfo.fsh_name,
FSInfo.device_name);
}
}
if (fs_stat_index (DeviceID, g_AttributeNameSpamRatio,
&IndexInfo) && errno == B_ENTRY_NOT_FOUND)
{
if (fs_create_index (DeviceID, g_AttributeNameSpamRatio,
B_FLOAT_TYPE, 0 ))
{
ErrorCode = errno;
sprintf (ErrorMessage, "Unable to make float index %s on "
"volume #%d, volume name \"%s\", file system type \"%s\", "
"on device \"%s\"", g_AttributeNameSpamRatio,
(int) DeviceID, FSInfo.volume_name, FSInfo.fsh_name,
FSInfo.device_name);
}
}
}
}
if (ErrorCode != B_OK)
return ErrorCode;
ErrorCode = MimeType.SetTo ("text/x-email");
if (ErrorCode != B_OK || !MimeType.IsInstalled ())
{
sprintf (ErrorMessage, "No e-mail MIME type (%s) in the system, can't "
"update it to add our special attributes, and without e-mail this "
"program is useless!", MimeType.Type ());
if (ErrorCode == B_OK)
ErrorCode = -1;
return ErrorCode;
}
ErrorCode = MimeType.GetAttrInfo (&Parameters);
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "Unable to retrieve list of attributes "
"associated with e-mail messages in the MIME database");
return ErrorCode;
}
for (i = 0, iClassification = -1, iProbability = -1;
i < 1000 && (iClassification < 0 || iProbability < 0);
i++)
{
ErrorCode = Parameters.FindString ("attr:name", i, &StringPntr);
if (ErrorCode != B_OK)
break;
if (strcmp (StringPntr, g_AttributeNameClassification) == 0)
iClassification = i;
else if (strcmp (StringPntr, g_AttributeNameSpamRatio) == 0)
iProbability = i;
}
i--;
for (j = 0; j <= i; j++)
{
if (Parameters.FindString ("attr:public_name", j, &StringPntr) ==
B_BAD_INDEX)
{
if (Parameters.FindString ("attr:name", j, &StringPntr) != B_OK)
StringPntr = "None!";
Parameters.AddString ("attr:public_name", StringPntr);
}
}
while (Parameters.FindInt32 ("attr:type", i, &TempInt32) == B_BAD_INDEX)
Parameters.AddInt32 ("attr:type", B_STRING_TYPE);
while (Parameters.FindBool ("attr:viewable", i, &TempBool) == B_BAD_INDEX)
Parameters.AddBool ("attr:viewable", true);
while (Parameters.FindBool ("attr:editable", i, &TempBool) == B_BAD_INDEX)
Parameters.AddBool ("attr:editable", false);
while (Parameters.FindInt32 ("attr:width", i, &TempInt32) == B_BAD_INDEX)
Parameters.AddInt32 ("attr:width", 60);
while (Parameters.FindInt32 ("attr:alignment", i, &TempInt32) == B_BAD_INDEX)
Parameters.AddInt32 ("attr:alignment", B_ALIGN_LEFT);
while (Parameters.FindBool ("attr:extra", i, &TempBool) == B_BAD_INDEX)
Parameters.AddBool ("attr:extra", false);
if (iClassification < 0)
{
Parameters.AddString ("attr:name", g_AttributeNameClassification);
Parameters.AddString ("attr:public_name", "Classification Group");
Parameters.AddInt32 ("attr:type", B_STRING_TYPE);
Parameters.AddBool ("attr:viewable", true);
Parameters.AddBool ("attr:editable", false);
Parameters.AddInt32 ("attr:width", 45);
Parameters.AddInt32 ("attr:alignment", B_ALIGN_LEFT);
Parameters.AddBool ("attr:extra", false);
}
if (iProbability < 0)
{
Parameters.AddString ("attr:name", g_AttributeNameSpamRatio);
Parameters.AddString ("attr:public_name", "Spam/Genuine Estimate");
Parameters.AddInt32 ("attr:type", B_FLOAT_TYPE);
Parameters.AddBool ("attr:viewable", true);
Parameters.AddBool ("attr:editable", false);
Parameters.AddInt32 ("attr:width", 50);
Parameters.AddInt32 ("attr:alignment", B_ALIGN_LEFT);
Parameters.AddBool ("attr:extra", false);
}
if (iClassification < 0 || iProbability < 0)
{
ErrorCode = MimeType.SetAttrInfo (&Parameters);
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "Unable to associate the classification "
"attributes with e-mail messages in the MIME database");
return ErrorCode;
}
}
sprintf (ErrorMessage, "Problems with setting up MIME type (%s) for "
"the database files", g_ABSDatabaseFileMIMEType);
ErrorCode = MimeType.SetTo (g_ABSDatabaseFileMIMEType);
if (ErrorCode != B_OK)
return ErrorCode;
MimeType.Delete ();
ErrorCode = MimeType.Install ();
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "Failed to install MIME type (%s) in the system",
MimeType.Type ());
return ErrorCode;
}
MimeType.SetShortDescription ("Spam Database");
MimeType.SetLongDescription ("Bayesian Statistical Database for "
"Classifying Junk E-Mail");
sprintf (ErrorMessage, "1.0 ('%s')", g_DatabaseRecognitionString);
MimeType.SetSnifferRule (ErrorMessage);
MimeType.SetPreferredApp (g_ABSAppSignature);
add_system_beep_event (g_BeepGenuine);
add_system_beep_event (g_BeepSpam);
add_system_beep_event (g_BeepUncertain);
return B_OK;
}
status_t ABSApp::LoadDatabaseIfNeeded (char *ErrorMessage)
{
if (m_WordMap.empty ())
return LoadSaveDatabase (true , ErrorMessage);
return B_OK;
}
status_t ABSApp::LoadSaveDatabase (bool DoLoad, char *ErrorMessage)
{
time_t CurrentTime;
FILE *DatabaseFile = NULL;
BNode DatabaseNode;
BNodeInfo DatabaseNodeInfo;
StatisticsMap::iterator DataIter;
StatisticsMap::iterator EndIter;
status_t ErrorCode;
int i;
pair<StatisticsMap::iterator,bool> InsertResult;
char LineString [10240];
StatisticsRecord Statistics;
const char *StringPntr;
char *TabPntr;
const char *WordPntr;
if (DoLoad)
{
MakeDatabaseEmpty ();
m_DatabaseHasChanged = false;
}
else
{
ErrorCode = MakeBackup (ErrorMessage);
if (ErrorCode != B_OK)
return ErrorCode;
}
DatabaseFile = fopen (m_DatabaseFileName.String (), DoLoad ? "rb" : "wb");
if (DatabaseFile == NULL)
{
ErrorCode = errno;
sprintf (ErrorMessage, "Can't open database file \"%s\" for %s",
m_DatabaseFileName.String (), DoLoad ? "reading" : "writing");
goto ErrorExit;
}
if (DoLoad)
{
sprintf (ErrorMessage, "Can't read first line of database file \"%s\", "
"expected it to start with \"%s\"",
m_DatabaseFileName.String (), g_DatabaseRecognitionString);
ErrorCode = -1;
if (fgets (LineString, sizeof (LineString), DatabaseFile) == NULL)
goto ErrorExit;
if (strncmp (LineString, g_DatabaseRecognitionString,
strlen (g_DatabaseRecognitionString)) != 0)
goto ErrorExit;
}
else
{
CurrentTime = time (NULL);
if (fprintf (DatabaseFile, "%s V1 (word, age, genuine count, spam count)\t"
"Written by SpamDBM $Revision: 30630 $\t"
"Compiled on " __DATE__ " at " __TIME__ "\tThis file saved on %s",
g_DatabaseRecognitionString, ctime (&CurrentTime)) <= 0)
{
ErrorCode = errno;
sprintf (ErrorMessage, "Problems when writing to database file \"%s\"",
m_DatabaseFileName.String ());
goto ErrorExit;
}
}
if (DoLoad)
{
sprintf (ErrorMessage, "Can't read second line of database file \"%s\", "
"expected it to list classifications %s and %s along with their totals",
m_DatabaseFileName.String (), g_ClassifiedGenuine, g_ClassifiedSpam);
ErrorCode = B_BAD_VALUE;
if (fgets (LineString, sizeof (LineString), DatabaseFile) == NULL)
goto ErrorExit;
i = strlen (LineString);
if (i > 0 && LineString[i-1] == '\n')
LineString[i-1] = 0;
TabPntr = LineString;
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
if (strncmp (StringPntr, "Classifications", 15) != 0)
goto ErrorExit;
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
if (strcmp (StringPntr, g_ClassifiedGenuine) != 0)
goto ErrorExit;
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
m_TotalGenuineMessages = atoll (StringPntr);
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
if (strcmp (StringPntr, g_ClassifiedSpam) != 0)
goto ErrorExit;
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
m_TotalSpamMessages = atoll (StringPntr);
}
else
{
fprintf (DatabaseFile,
"Classifications and total messages:\t%s\t%" B_PRIu32
"\t%s\t%" B_PRIu32 "\n",
g_ClassifiedGenuine, m_TotalGenuineMessages,
g_ClassifiedSpam, m_TotalSpamMessages);
}
if (DoLoad)
{
while (!feof (DatabaseFile))
{
if (fgets (LineString, sizeof (LineString), DatabaseFile) == NULL)
{
ErrorCode = errno;
if (feof (DatabaseFile))
break;
if (ErrorCode == B_OK)
ErrorCode = -1;
sprintf (ErrorMessage, "Error while reading words and statistics "
"from database file \"%s\"", m_DatabaseFileName.String ());
goto ErrorExit;
}
i = strlen (LineString);
if (i > 0 && LineString[i-1] == '\n')
LineString[i-1] = 0;
TabPntr = LineString;
for (WordPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
Statistics.age = atoll (StringPntr);
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
Statistics.genuineCount = atoll (StringPntr);
for (StringPntr = TabPntr; *TabPntr != 0 && *TabPntr != '\t'; TabPntr++)
;
if (*TabPntr == '\t') *TabPntr++ = 0;
Statistics.spamCount = atoll (StringPntr);
if (WordPntr[0] == 0 || strlen (WordPntr) > g_MaxWordLength ||
(Statistics.genuineCount <= 0 && Statistics.spamCount <= 0))
continue;
InsertResult = m_WordMap.insert (
StatisticsMap::value_type (WordPntr, Statistics));
if (InsertResult.second == false)
{
ErrorCode = B_BAD_VALUE;
sprintf (ErrorMessage, "Error while inserting word \"%s\" from "
"database \"%s\", perhaps it is a duplicate",
WordPntr, m_DatabaseFileName.String ());
goto ErrorExit;
}
m_WordCount++;
if (Statistics.age < m_OldestAge)
m_OldestAge = Statistics.age;
}
}
else
{
EndIter = m_WordMap.end ();
for (DataIter = m_WordMap.begin (); DataIter != EndIter; DataIter++)
{
if (fprintf (DatabaseFile,
"%s\t%" B_PRIu32 "\t%" B_PRIu32 "\t%" B_PRIu32 "\n",
DataIter->first.c_str (), DataIter->second.age,
DataIter->second.genuineCount, DataIter->second.spamCount) <= 0)
{
ErrorCode = errno;
sprintf (ErrorMessage, "Error while writing word \"%s\" to "
"database \"%s\"",
DataIter->first.c_str(), m_DatabaseFileName.String ());
goto ErrorExit;
}
}
}
if (!DoLoad)
{
sprintf (ErrorMessage, "Unable to set attributes (file type) of database "
"file \"%s\"", m_DatabaseFileName.String ());
ErrorCode = DatabaseNode.SetTo (m_DatabaseFileName.String ());
if (ErrorCode != B_OK)
goto ErrorExit;
DatabaseNodeInfo.SetTo (&DatabaseNode);
ErrorCode = DatabaseNodeInfo.SetType (g_ABSDatabaseFileMIMEType);
if (ErrorCode != B_OK)
goto ErrorExit;
}
m_DatabaseHasChanged = false;
ErrorCode = B_OK;
ErrorExit:
if (DatabaseFile != NULL)
fclose (DatabaseFile);
return ErrorCode;
}
status_t ABSApp::LoadSaveSettings (bool DoLoad)
{
status_t ErrorCode;
const char *NamePntr;
BMessage Settings;
BDirectory SettingsDirectory;
BFile SettingsFile;
const char *StringPntr;
bool TempBool;
int32 TempInt32;
char TempString [PATH_MAX + 100];
if (DoLoad)
DefaultSettings ();
ErrorCode = SettingsDirectory.SetTo (m_SettingsDirectoryPath.Path ());
if (ErrorCode != B_OK)
{
if (DoLoad || ErrorCode != B_ENTRY_NOT_FOUND)
{
sprintf (TempString, "Can't find settings directory \"%s\"",
m_SettingsDirectoryPath.Path ());
goto ErrorExit;
}
ErrorCode = create_directory (m_SettingsDirectoryPath.Path (), 0755);
if (ErrorCode == B_OK)
ErrorCode = SettingsDirectory.SetTo (m_SettingsDirectoryPath.Path ());
if (ErrorCode != B_OK)
{
sprintf (TempString, "Can't create settings directory \"%s\"",
m_SettingsDirectoryPath.Path ());
goto ErrorExit;
}
}
ErrorCode = SettingsFile.SetTo (&SettingsDirectory, g_SettingsFileName,
DoLoad ? B_READ_ONLY : B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
if (ErrorCode != B_OK)
{
sprintf (TempString, "Can't open settings file \"%s\" in directory \"%s\" "
"for %s", g_SettingsFileName, m_SettingsDirectoryPath.Path(),
DoLoad ? "reading" : "writing");
goto ErrorExit;
}
if (DoLoad)
{
ErrorCode = Settings.Unflatten (&SettingsFile);
if (ErrorCode != 0 || Settings.what != g_SettingsWhatCode)
{
sprintf (TempString, "Corrupt data detected while reading settings "
"file \"%s\" in directory \"%s\", will revert to defaults",
g_SettingsFileName, m_SettingsDirectoryPath.Path());
goto ErrorExit;
}
}
ErrorCode = B_OK;
NamePntr = "DatabaseFileName";
if (DoLoad)
{
if (Settings.FindString (NamePntr, &StringPntr) == B_OK)
m_DatabaseFileName.SetTo (StringPntr);
}
else if (ErrorCode == B_OK)
ErrorCode = Settings.AddString (NamePntr, m_DatabaseFileName);
NamePntr = "ServerMode";
if (DoLoad)
{
if (Settings.FindBool (NamePntr, &TempBool) == B_OK)
g_ServerMode = TempBool;
}
else if (ErrorCode == B_OK)
ErrorCode = Settings.AddBool (NamePntr, g_ServerMode);
NamePntr = "IgnorePreviousClassification";
if (DoLoad)
{
if (Settings.FindBool (NamePntr, &TempBool) == B_OK)
m_IgnorePreviousClassification = TempBool;
}
else if (ErrorCode == B_OK)
ErrorCode = Settings.AddBool (NamePntr, m_IgnorePreviousClassification);
NamePntr = "PurgeAge";
if (DoLoad)
{
if (Settings.FindInt32 (NamePntr, &TempInt32) == B_OK)
m_PurgeAge = TempInt32;
}
else if (ErrorCode == B_OK)
ErrorCode = Settings.AddInt32 (NamePntr, m_PurgeAge);
NamePntr = "PurgePopularity";
if (DoLoad)
{
if (Settings.FindInt32 (NamePntr, &TempInt32) == B_OK)
m_PurgePopularity = TempInt32;
}
else if (ErrorCode == B_OK)
ErrorCode = Settings.AddInt32 (NamePntr, m_PurgePopularity);
NamePntr = "ScoringMode";
if (DoLoad)
{
if (Settings.FindInt32 (NamePntr, &TempInt32) == B_OK)
m_ScoringMode = (ScoringModes) TempInt32;
if (m_ScoringMode < 0 || m_ScoringMode >= SM_MAX)
m_ScoringMode = (ScoringModes) 0;
}
else if (ErrorCode == B_OK)
ErrorCode = Settings.AddInt32 (NamePntr, m_ScoringMode);
NamePntr = "TokenizeMode";
if (DoLoad)
{
if (Settings.FindInt32 (NamePntr, &TempInt32) == B_OK)
m_TokenizeMode = (TokenizeModes) TempInt32;
if (m_TokenizeMode < 0 || m_TokenizeMode >= TM_MAX)
m_TokenizeMode = (TokenizeModes) 0;
}
else if (ErrorCode == B_OK)
ErrorCode = Settings.AddInt32 (NamePntr, m_TokenizeMode);
if (ErrorCode != B_OK)
{
strcpy (TempString, "Unable to stuff the program settings into a "
"temporary BMessage, settings not saved");
goto ErrorExit;
}
if (!DoLoad)
{
Settings.what = g_SettingsWhatCode;
ErrorCode = Settings.Flatten (&SettingsFile);
if (ErrorCode != 0)
{
sprintf (TempString, "Problems while writing settings file \"%s\" in "
"directory \"%s\"", g_SettingsFileName,
m_SettingsDirectoryPath.Path ());
goto ErrorExit;
}
}
m_SettingsHaveChanged = false;
return B_OK;
ErrorExit:
DisplayErrorMessage (TempString, ErrorCode, DoLoad ?
"Loading Settings Error" : "Saving Settings Error");
return ErrorCode;
}
void
ABSApp::MessageReceived (BMessage *MessagePntr)
{
const char *PropertyName;
struct property_info *PropInfoPntr;
int32 SpecifierIndex;
int32 SpecifierKind;
BMessage SpecifierMessage;
switch (MessagePntr->what)
{
case B_GET_PROPERTY:
case B_SET_PROPERTY:
case B_COUNT_PROPERTIES:
case B_CREATE_PROPERTY:
case B_DELETE_PROPERTY:
case B_EXECUTE_PROPERTY:
if (MessagePntr->GetCurrentSpecifier (&SpecifierIndex, &SpecifierMessage,
&SpecifierKind, &PropertyName) == B_OK &&
SpecifierKind == B_DIRECT_SPECIFIER)
{
for (PropInfoPntr = g_ScriptingPropertyList + 0; true; PropInfoPntr++)
{
if (PropInfoPntr->name == 0)
break;
if (PropInfoPntr->commands[0] == MessagePntr->what &&
strcasecmp (PropInfoPntr->name, PropertyName) == 0)
{
ProcessScriptingMessage (MessagePntr, PropInfoPntr);
return;
}
}
}
break;
}
BApplication::MessageReceived (MessagePntr);
}
status_t ABSApp::MakeBackup (char *ErrorMessage)
{
BEntry Entry;
status_t ErrorCode;
int i;
char LeafName [NAME_MAX];
char NewName [PATH_MAX+20];
char OldName [PATH_MAX+20];
ErrorCode = Entry.SetTo (m_DatabaseFileName.String ());
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "While making backup, failed to make a BEntry for "
"\"%s\" (maybe the directory doesn't exist?)",
m_DatabaseFileName.String ());
return ErrorCode;
}
if (!Entry.Exists ())
return B_OK;
Entry.GetName (LeafName);
for (i = 0; i < g_MaxBackups - 1; i++)
{
strcpy (OldName, m_DatabaseFileName.String ());
sprintf (OldName + strlen (OldName), g_BackupSuffix, i);
Entry.SetTo (OldName);
if (!Entry.Exists ())
break;
}
for (i--; i >= 0; i--)
{
strcpy (OldName, m_DatabaseFileName.String ());
sprintf (OldName + strlen (OldName), g_BackupSuffix, i);
Entry.SetTo (OldName);
strcpy (NewName, LeafName);
sprintf (NewName + strlen (NewName), g_BackupSuffix, i + 1);
ErrorCode = Entry.Rename (NewName, true );
}
Entry.SetTo (m_DatabaseFileName.String ());
strcpy (NewName, LeafName);
sprintf (NewName + strlen (NewName), g_BackupSuffix, 0);
ErrorCode = Entry.Rename (NewName, true );
if (ErrorCode != B_OK)
sprintf (ErrorMessage, "While making backup, failed to rename "
"\"%s\" to \"%s\"", m_DatabaseFileName.String (), NewName);
return ErrorCode;
}
void
ABSApp::MakeDatabaseEmpty ()
{
m_WordMap.clear ();
m_WordCount = 0;
m_TotalGenuineMessages = 0;
m_TotalSpamMessages = 0;
m_OldestAge = (uint32) -1 ;
}
void
ABSApp::ProcessScriptingMessage (
BMessage *MessagePntr,
struct property_info *PropInfoPntr)
{
bool ArgumentBool = false;
bool ArgumentGotBool = false;
bool ArgumentGotInt32 = false;
bool ArgumentGotString = false;
int32 ArgumentInt32 = 0;
const char *ArgumentString = NULL;
BString CommandText;
status_t ErrorCode;
int i;
BMessage ReplyMessage (B_MESSAGE_NOT_UNDERSTOOD);
ssize_t StringBufferSize;
BMessage TempBMessage;
BPath TempPath;
char TempString [PATH_MAX + 1024];
if (g_QuitCountdown >= 0 && !g_CommandLineMode)
{
g_QuitCountdown = -1;
cerr << "Quit countdown aborted due to a scripting command arriving.\n";
}
if (g_BusyCursor != NULL)
SetCursor (g_BusyCursor);
ErrorCode = MessagePntr->FindData (g_DataName, B_STRING_TYPE,
(const void **) &ArgumentString, &StringBufferSize);
if (ErrorCode == B_OK)
{
if (PropInfoPntr->extra_data != PN_EVALUATE_STRING &&
PropInfoPntr->extra_data != PN_SPAM_STRING &&
PropInfoPntr->extra_data != PN_GENUINE_STRING &&
strlen (ArgumentString) >= PATH_MAX)
{
sprintf (TempString, "\"data\" string of a scripting message is too "
"long, for SET %s action", PropInfoPntr->name);
ErrorCode = B_NAME_TOO_LONG;
goto ErrorExit;
}
ArgumentGotString = true;
}
else if (MessagePntr->FindBool (g_DataName, &ArgumentBool) == B_OK)
ArgumentGotBool = true;
else if (MessagePntr->FindInt32 (g_DataName, &ArgumentInt32) == B_OK)
ArgumentGotInt32 = true;
switch (PropInfoPntr->commands[0])
{
case B_SET_PROPERTY:
CommandText.SetTo ("Set ");
break;
case B_GET_PROPERTY:
CommandText.SetTo ("Get ");
break;
case B_COUNT_PROPERTIES:
CommandText.SetTo ("Count ");
break;
case B_CREATE_PROPERTY:
CommandText.SetTo ("Create ");
break;
case B_DELETE_PROPERTY:
CommandText.SetTo ("Delete ");
break;
case B_EXECUTE_PROPERTY:
CommandText.SetTo ("Execute ");
break;
default:
sprintf (TempString, "Bug: scripting command for \"%s\" has an unknown "
"action code %d", PropInfoPntr->name,
(int) PropInfoPntr->commands[0]);
ErrorCode = -1;
goto ErrorExit;
}
CommandText.Append (PropInfoPntr->name);
if (ArgumentGotString)
{
CommandText.Append (" \"");
CommandText.Append (ArgumentString);
CommandText.Append ("\"");
}
if (ArgumentGotBool)
CommandText.Append (ArgumentBool ? " true" : " false");
if (ArgumentGotInt32)
{
sprintf (TempString, " %" B_PRId32, ArgumentInt32);
CommandText.Append (TempString);
}
ReplyMessage.what = B_REPLY;
ReplyMessage.AddString ("CommandText", CommandText);
sprintf (TempString, "Operation code %d (get, set, count, etc) "
"unsupported for property %s",
(int) PropInfoPntr->commands[0], PropInfoPntr->name);
ErrorCode = B_BAD_INDEX;
switch (PropInfoPntr->extra_data)
{
case PN_DATABASE_FILE:
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
ReplyMessage.AddString (g_ResultName, m_DatabaseFileName);
break;
case B_SET_PROPERTY:
if (!ArgumentGotString)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You need to specify a string for the "
"SET %s command", PropInfoPntr->name);
goto ErrorExit;
}
ErrorCode = TempPath.SetTo (ArgumentString, NULL ,
true );
if (ErrorCode != B_OK)
{
sprintf (TempString, "New database path name of \"%s\" is invalid "
"(parent directories must exist)", ArgumentString);
goto ErrorExit;
}
if ((ErrorCode = SaveDatabaseIfNeeded (TempString)) != B_OK)
goto ErrorExit;
MakeDatabaseEmpty ();
if (strlen (TempPath.Leaf ()) > NAME_MAX-strlen(g_BackupSuffix)-1)
{
strcpy (TempString, TempPath.Leaf ());
TempString [NAME_MAX - strlen (g_BackupSuffix) - 1] = 0;
TempPath.GetParent (&TempPath);
TempPath.Append (TempString);
}
m_DatabaseFileName.SetTo (TempPath.Path ());
m_SettingsHaveChanged = true;
break;
case B_CREATE_PROPERTY:
if ((ErrorCode = CreateDatabaseFile (TempString)) != B_OK)
goto ErrorExit;
break;
case B_DELETE_PROPERTY:
if ((ErrorCode = DeleteDatabaseFile (TempString)) != B_OK)
goto ErrorExit;
break;
case B_COUNT_PROPERTIES:
if ((ErrorCode = LoadDatabaseIfNeeded (TempString)) != B_OK)
goto ErrorExit;
ReplyMessage.AddInt32 (g_ResultName, m_WordCount);
break;
default:
goto ErrorExit;
}
break;
case PN_SPAM:
case PN_SPAM_STRING:
case PN_GENUINE:
case PN_GENUINE_STRING:
case PN_UNCERTAIN:
switch (PropInfoPntr->commands[0])
{
case B_COUNT_PROPERTIES:
if ((ErrorCode = LoadDatabaseIfNeeded (TempString)) != B_OK)
goto ErrorExit;
if (PropInfoPntr->extra_data == PN_SPAM ||
PropInfoPntr->extra_data == PN_SPAM_STRING)
ReplyMessage.AddInt32 (g_ResultName, m_TotalSpamMessages);
else
ReplyMessage.AddInt32 (g_ResultName, m_TotalGenuineMessages);
break;
case B_SET_PROPERTY:
if (!ArgumentGotString)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You need to specify a string (%s) "
"for the SET %s command",
(PropInfoPntr->extra_data == PN_GENUINE_STRING ||
PropInfoPntr->extra_data == PN_SPAM_STRING)
? "text of the message to be added"
: "pathname of the file containing the text to be added",
PropInfoPntr->name);
goto ErrorExit;
}
if ((ErrorCode = LoadDatabaseIfNeeded (TempString)) != B_OK)
goto ErrorExit;
if (PropInfoPntr->extra_data == PN_GENUINE ||
PropInfoPntr->extra_data == PN_SPAM ||
PropInfoPntr->extra_data == PN_UNCERTAIN)
ErrorCode = AddFileToDatabase (
(PropInfoPntr->extra_data == PN_SPAM) ? CL_SPAM :
((PropInfoPntr->extra_data == PN_GENUINE) ? CL_GENUINE :
CL_UNCERTAIN),
ArgumentString, TempString );
else
ErrorCode = AddStringToDatabase (
(PropInfoPntr->extra_data == PN_SPAM_STRING) ?
CL_SPAM : CL_GENUINE,
ArgumentString, TempString );
if (ErrorCode != B_OK)
goto ErrorExit;
break;
default:
goto ErrorExit;
}
break;
case PN_IGNORE_PREVIOUS_CLASSIFICATION:
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
ReplyMessage.AddBool (g_ResultName, m_IgnorePreviousClassification);
break;
case B_SET_PROPERTY:
if (!ArgumentGotBool)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You need to specify a boolean (true/yes, "
"false/no) for the SET %s command", PropInfoPntr->name);
goto ErrorExit;
}
m_IgnorePreviousClassification = ArgumentBool;
m_SettingsHaveChanged = true;
break;
default:
goto ErrorExit;
}
break;
case PN_SERVER_MODE:
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
ReplyMessage.AddBool (g_ResultName, g_ServerMode);
break;
case B_SET_PROPERTY:
if (!ArgumentGotBool)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You need to specify a boolean (true/yes, "
"false/no) for the SET %s command", PropInfoPntr->name);
goto ErrorExit;
}
g_ServerMode = ArgumentBool;
m_SettingsHaveChanged = true;
break;
default:
goto ErrorExit;
}
break;
case PN_FLUSH:
if (PropInfoPntr->commands[0] == B_EXECUTE_PROPERTY &&
(ErrorCode = SaveDatabaseIfNeeded (TempString)) == B_OK)
break;
goto ErrorExit;
case PN_PURGE_AGE:
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
ReplyMessage.AddInt32 (g_ResultName, m_PurgeAge);
break;
case B_SET_PROPERTY:
if (!ArgumentGotInt32)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You need to specify a 32 bit integer "
"for the SET %s command", PropInfoPntr->name);
goto ErrorExit;
}
m_PurgeAge = ArgumentInt32;
m_SettingsHaveChanged = true;
break;
default:
goto ErrorExit;
}
break;
case PN_PURGE_POPULARITY:
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
ReplyMessage.AddInt32 (g_ResultName, m_PurgePopularity);
break;
case B_SET_PROPERTY:
if (!ArgumentGotInt32)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You need to specify a 32 bit integer "
"for the SET %s command", PropInfoPntr->name);
goto ErrorExit;
}
m_PurgePopularity = ArgumentInt32;
m_SettingsHaveChanged = true;
break;
default:
goto ErrorExit;
}
break;
case PN_PURGE:
if (PropInfoPntr->commands[0] == B_EXECUTE_PROPERTY &&
(ErrorCode = LoadDatabaseIfNeeded (TempString)) == B_OK &&
(ErrorCode = PurgeOldWords (TempString)) == B_OK)
break;
goto ErrorExit;
case PN_OLDEST:
if (PropInfoPntr->commands[0] == B_GET_PROPERTY &&
(ErrorCode = LoadDatabaseIfNeeded (TempString)) == B_OK)
{
ReplyMessage.AddInt32 (g_ResultName, m_OldestAge);
break;
}
goto ErrorExit;
case PN_EVALUATE:
case PN_EVALUATE_STRING:
if (PropInfoPntr->commands[0] == B_SET_PROPERTY)
{
if (!ArgumentGotString)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You need to specify a string for the "
"SET %s command", PropInfoPntr->name);
goto ErrorExit;
}
if ((ErrorCode = LoadDatabaseIfNeeded (TempString)) == B_OK)
{
if (PropInfoPntr->extra_data == PN_EVALUATE)
{
if ((ErrorCode = EvaluateFile (ArgumentString, &ReplyMessage,
TempString)) == B_OK)
break;
}
else
{
if ((ErrorCode = EvaluateString (ArgumentString, StringBufferSize,
&ReplyMessage, TempString)) == B_OK)
break;
}
}
}
goto ErrorExit;
case PN_RESET_TO_DEFAULTS:
if (PropInfoPntr->commands[0] == B_EXECUTE_PROPERTY)
{
DefaultSettings ();
break;
}
goto ErrorExit;
case PN_INSTALL_THINGS:
if (PropInfoPntr->commands[0] == B_EXECUTE_PROPERTY &&
(ErrorCode = InstallThings (TempString)) == B_OK)
break;
goto ErrorExit;
case PN_SCORING_MODE:
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
ReplyMessage.AddString (g_ResultName,
g_ScoringModeNames[m_ScoringMode]);
break;
case B_SET_PROPERTY:
i = SM_MAX;
if (ArgumentGotString)
for (i = 0; i < SM_MAX; i++)
{
if (strcasecmp (ArgumentString, g_ScoringModeNames [i]) == 0)
{
m_ScoringMode = (ScoringModes) i;
m_SettingsHaveChanged = true;
break;
}
}
if (i >= SM_MAX)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You used the unrecognized \"%s\" as "
"a scoring mode for the SET %s command. Should be one of: ",
ArgumentGotString ? ArgumentString : "not specified",
PropInfoPntr->name);
for (i = 0; i < SM_MAX; i++)
{
strcat (TempString, g_ScoringModeNames [i]);
if (i < SM_MAX - 1)
strcat (TempString, ", ");
}
goto ErrorExit;
}
break;
default:
goto ErrorExit;
}
break;
case PN_TOKENIZE_MODE:
switch (PropInfoPntr->commands[0])
{
case B_GET_PROPERTY:
ReplyMessage.AddString (g_ResultName,
g_TokenizeModeNames[m_TokenizeMode]);
break;
case B_SET_PROPERTY:
i = TM_MAX;
if (ArgumentGotString)
for (i = 0; i < TM_MAX; i++)
{
if (strcasecmp (ArgumentString, g_TokenizeModeNames [i]) == 0)
{
m_TokenizeMode = (TokenizeModes) i;
m_SettingsHaveChanged = true;
break;
}
}
if (i >= TM_MAX)
{
ErrorCode = B_BAD_TYPE;
sprintf (TempString, "You used the unrecognized \"%s\" as "
"a tokenize mode for the SET %s command. Should be one of: ",
ArgumentGotString ? ArgumentString : "not specified",
PropInfoPntr->name);
for (i = 0; i < TM_MAX; i++)
{
strcat (TempString, g_TokenizeModeNames [i]);
if (i < TM_MAX - 1)
strcat (TempString, ", ");
}
goto ErrorExit;
}
break;
default:
goto ErrorExit;
}
break;
default:
sprintf (TempString, "Bug! Unrecognized property identification "
"number %d (should be between 0 and %d). Fix the entry in "
"the g_ScriptingPropertyList array!",
(int) PropInfoPntr->extra_data, PN_MAX - 1);
goto ErrorExit;
}
ReplyMessage.AddInt32 ("error", B_OK);
ErrorCode = MessagePntr->SendReply (&ReplyMessage,
this , 500000 );
if (ErrorCode != B_OK)
cerr << "ProcessScriptingMessage failed to send a reply message, code " <<
ErrorCode << " (" << strerror (ErrorCode) << ")" << " for " <<
CommandText.String () << endl;
SetCursor (B_CURSOR_SYSTEM_DEFAULT);
return;
ErrorExit:
ReplyMessage.AddInt32 ("error", ErrorCode);
ReplyMessage.AddString ("message", TempString);
DisplayErrorMessage (TempString, ErrorCode);
ErrorCode = MessagePntr->SendReply (&ReplyMessage,
this , 500000 );
if (ErrorCode != B_OK)
cerr << "ProcessScriptingMessage failed to send an error message, code " <<
ErrorCode << " (" << strerror (ErrorCode) << ")" << " for " <<
CommandText.String () << endl;
SetCursor (B_CURSOR_SYSTEM_DEFAULT);
}
void
ABSApp::Pulse ()
{
if (g_QuitCountdown == 0)
{
if (g_CommanderLooperPntr == NULL ||
!g_CommanderLooperPntr->IsBusy ())
PostMessage (B_QUIT_REQUESTED);
}
else if (g_QuitCountdown > 0)
{
cerr << "SpamDBM quitting in " << g_QuitCountdown << ".\n";
g_QuitCountdown--;
}
}
bool
ABSApp::QuitRequested ()
{
BMessage *QuitMessage;
team_info RemoteInfo;
BMessenger RemoteMessenger;
team_id RemoteTeam;
QuitMessage = CurrentMessage ();
if (QuitMessage != NULL && QuitMessage->IsSourceRemote ())
{
RemoteMessenger = QuitMessage->ReturnAddress ();
RemoteTeam = RemoteMessenger.Team ();
if (get_team_info (RemoteTeam, &RemoteInfo) == B_OK &&
strstr (RemoteInfo.args, "registrar") != NULL)
g_QuitCountdown = 0;
}
if (g_QuitCountdown == 0)
return BApplication::QuitRequested ();
if (g_QuitCountdown < 0)
g_QuitCountdown = 5;
return false;
}
status_t
ABSApp::PurgeOldWords (char *ErrorMessage)
{
uint32 CurrentTime;
StatisticsMap::iterator CurrentIter;
StatisticsMap::iterator EndIter;
StatisticsMap::iterator NextIter;
char TempString [80];
strcpy (ErrorMessage, "Purge can't fail");
CurrentTime = m_TotalGenuineMessages + m_TotalSpamMessages - 1;
m_OldestAge = (uint32) -1 ;
EndIter = m_WordMap.end ();
NextIter = m_WordMap.begin ();
while (NextIter != EndIter) {
CurrentIter = NextIter++;
if (CurrentTime - CurrentIter->second.age >= m_PurgeAge &&
CurrentIter->second.genuineCount + CurrentIter->second.spamCount <=
m_PurgePopularity) {
m_WordMap.erase (CurrentIter);
if (m_WordCount > 0)
m_WordCount--;
m_DatabaseHasChanged = true;
}
else
{
if (CurrentIter->second.age < m_OldestAge)
m_OldestAge = CurrentIter->second.age;
}
}
if (m_WordCount != m_WordMap.size ()) {
sprintf (TempString, "Our word count of %" B_PRIu32 " doesn't match the "
"size of the database, %lu", m_WordCount, m_WordMap.size());
DisplayErrorMessage (TempString, -1, "Bug!");
m_WordCount = m_WordMap.size ();
}
return B_OK;
}
void
ABSApp::ReadyToRun ()
{
DatabaseWindow *DatabaseWindowPntr;
float JunkFloat;
BButton *TempButtonPntr;
BCheckBox *TempCheckBoxPntr;
font_height TempFontHeight;
BMenuBar *TempMenuBarPntr;
BMenuItem *TempMenuItemPntr;
BPopUpMenu *TempPopUpMenuPntr;
BRadioButton *TempRadioButtonPntr;
BRect TempRect;
const char *TempString = "Testing My Things";
BStringView *TempStringViewPntr;
BTextControl *TempTextPntr;
BWindow *TempWindowPntr;
g_MarginBetweenControls = (int) be_plain_font->StringWidth ("M");
be_plain_font->GetHeight (&TempFontHeight);
g_LineOfTextHeight = ceilf (
TempFontHeight.ascent + TempFontHeight.descent + TempFontHeight.leading);
TempWindowPntr = new (std::nothrow) BWindow (BRect (10, 20, 200, 200),
"Temporary Window", B_DOCUMENT_WINDOW,
B_NO_WORKSPACE_ACTIVATION | B_ASYNCHRONOUS_CONTROLS);
if (TempWindowPntr == NULL) {
DisplayErrorMessage ("Unable to create temporary window for finding "
"sizes of controls.");
g_QuitCountdown = 0;
return;
}
TempRect = TempWindowPntr->Bounds ();
TempStringViewPntr = new (std::nothrow) BStringView (TempRect, TempString, TempString);
if (TempStringViewPntr != NULL) {
TempWindowPntr->Lock();
TempWindowPntr->AddChild (TempStringViewPntr);
TempStringViewPntr->GetPreferredSize (&JunkFloat, &g_StringViewHeight);
TempWindowPntr->RemoveChild (TempStringViewPntr);
TempWindowPntr->Unlock();
delete TempStringViewPntr;
}
TempButtonPntr = new (std::nothrow) BButton (TempRect, TempString, TempString, NULL);
if (TempButtonPntr != NULL) {
TempWindowPntr->Lock();
TempWindowPntr->AddChild (TempButtonPntr);
TempButtonPntr->GetPreferredSize (&JunkFloat, &g_ButtonHeight);
TempWindowPntr->RemoveChild (TempButtonPntr);
TempWindowPntr->Unlock();
delete TempButtonPntr;
}
TempTextPntr = new (std::nothrow) BTextControl (TempRect, TempString, NULL ,
TempString, NULL);
if (TempTextPntr != NULL) {
TempWindowPntr->Lock ();
TempWindowPntr->AddChild (TempTextPntr);
TempTextPntr->GetPreferredSize (&JunkFloat, &g_TextBoxHeight);
TempWindowPntr->RemoveChild (TempTextPntr);
TempWindowPntr->Unlock ();
delete TempTextPntr;
}
TempCheckBoxPntr = new (std::nothrow) BCheckBox (TempRect, TempString, TempString, NULL);
if (TempCheckBoxPntr != NULL) {
TempWindowPntr->Lock ();
TempWindowPntr->AddChild (TempCheckBoxPntr);
TempCheckBoxPntr->GetPreferredSize (&JunkFloat, &g_CheckBoxHeight);
TempWindowPntr->RemoveChild (TempCheckBoxPntr);
TempWindowPntr->Unlock ();
delete TempCheckBoxPntr;
}
TempRadioButtonPntr =
new (std::nothrow) BRadioButton (TempRect, TempString, TempString, NULL);
if (TempRadioButtonPntr != NULL) {
TempWindowPntr->Lock ();
TempWindowPntr->AddChild (TempRadioButtonPntr);
TempRadioButtonPntr->GetPreferredSize (&JunkFloat, &g_RadioButtonHeight);
TempWindowPntr->RemoveChild (TempRadioButtonPntr);
TempWindowPntr->Unlock ();
delete TempRadioButtonPntr;
}
TempMenuBarPntr = new (std::nothrow) BMenuBar (TempRect, TempString,
B_FOLLOW_LEFT | B_FOLLOW_TOP, B_ITEMS_IN_COLUMN,
true );
TempPopUpMenuPntr = new (std::nothrow) BPopUpMenu (TempString);
TempMenuItemPntr = new (std::nothrow) BMenuItem (TempString, new BMessage (12345), 'g');
if (TempMenuBarPntr != NULL && TempPopUpMenuPntr != NULL &&
TempMenuItemPntr != NULL)
{
TempPopUpMenuPntr->AddItem (TempMenuItemPntr);
TempMenuBarPntr->AddItem (TempPopUpMenuPntr);
TempWindowPntr->Lock ();
TempWindowPntr->AddChild (TempMenuBarPntr);
TempMenuBarPntr->GetPreferredSize (&JunkFloat, &g_PopUpMenuHeight);
TempWindowPntr->RemoveChild (TempMenuBarPntr);
TempWindowPntr->Unlock ();
delete TempMenuBarPntr;
}
TempWindowPntr->Lock ();
TempWindowPntr->Quit ();
SetPulseRate (500000);
if (g_CommandLineMode)
g_QuitCountdown = 0;
else
{
DatabaseWindowPntr = new (std::nothrow) DatabaseWindow ();
if (DatabaseWindowPntr == NULL) {
DisplayErrorMessage ("Unable to create window.");
g_QuitCountdown = 0;
} else {
DatabaseWindowPntr->Show ();
}
}
g_AppReadyToRunCompleted = true;
}
status_t
ABSApp::RecursivelyTokenizeMailComponent (
BMailComponent *ComponentPntr,
const char *OptionalFileName,
set<string> &WordSet,
char *ErrorMessage,
int RecursionLevel,
int MaxRecursionLevel)
{
char AttachmentName [B_FILE_NAME_LENGTH];
BMailAttachment *AttachmentPntr;
BMimeType ComponentMIMEType;
BMailContainer *ContainerPntr;
BMallocIO ContentsIO;
const char *ContentsBufferPntr;
size_t ContentsBufferSize;
status_t ErrorCode;
bool ExamineComponent;
const char *HeaderKeyPntr;
const char *HeaderValuePntr;
int i;
int j;
const char *NameExtension;
int NumComponents;
BMimeType TextAnyMIMEType ("text");
BMimeType TextPlainMIMEType ("text/plain");
if (ComponentPntr == NULL)
return B_OK;
if (m_TokenizeMode == TM_PLAIN_TEXT_HEADER ||
m_TokenizeMode == TM_ANY_TEXT_HEADER ||
m_TokenizeMode == TM_ALL_PARTS_HEADER ||
m_TokenizeMode == TM_JUST_HEADER)
{
for (i = 0; i < 1000; i++)
{
HeaderKeyPntr = ComponentPntr->HeaderAt (i);
if (HeaderKeyPntr == NULL)
break;
AddWordsToSet (HeaderKeyPntr, strlen (HeaderKeyPntr),
'H' , WordSet);
for (j = 0; j < 1000; j++)
{
HeaderValuePntr = ComponentPntr->HeaderField (HeaderKeyPntr, j);
if (HeaderValuePntr == NULL)
break;
AddWordsToSet (HeaderValuePntr, strlen (HeaderValuePntr),
'H', WordSet);
}
}
}
ErrorCode = ComponentPntr->MIMEType (&ComponentMIMEType);
if (ErrorCode != B_OK)
{
sprintf (ErrorMessage, "ABSApp::RecursivelyTokenizeMailComponent: "
"Unable to get MIME type at level %d in \"%s\"",
RecursionLevel, OptionalFileName);
return ErrorCode;
}
if (ComponentMIMEType.Type() == NULL)
{
if (NULL != dynamic_cast<BTextMailComponent *>(ComponentPntr))
ComponentMIMEType.SetType ("text/plain");
}
if (!TextAnyMIMEType.Contains (&ComponentMIMEType) &&
NULL != (AttachmentPntr = dynamic_cast<BMailAttachment *>(ComponentPntr)))
{
NameExtension = NULL;
if (AttachmentPntr->FileName (AttachmentName) >= 0)
NameExtension = strrchr (AttachmentName, '.');
if (NameExtension != NULL)
{
if (strcasecmp (NameExtension, ".txt") == 0)
ComponentMIMEType.SetType ("text/plain");
else if (strcasecmp (NameExtension, ".htm") == 0 ||
strcasecmp (NameExtension, ".html") == 0)
ComponentMIMEType.SetType ("text/html");
}
}
switch (m_TokenizeMode)
{
case TM_PLAIN_TEXT:
case TM_PLAIN_TEXT_HEADER:
ExamineComponent = TextPlainMIMEType.Contains (&ComponentMIMEType);
break;
case TM_ANY_TEXT:
case TM_ANY_TEXT_HEADER:
ExamineComponent = TextAnyMIMEType.Contains (&ComponentMIMEType);
break;
case TM_ALL_PARTS:
case TM_ALL_PARTS_HEADER:
ExamineComponent = true;
break;
default:
ExamineComponent = false;
break;
}
if (ExamineComponent)
{
ContentsIO.SetBlockSize (16 * 1024);
ErrorCode = ComponentPntr->GetDecodedData (&ContentsIO);
if (ErrorCode == B_OK)
{
ContentsBufferPntr = (const char *) ContentsIO.Buffer ();
ContentsBufferSize = ContentsIO.BufferLength ();
if (ContentsBufferPntr != NULL )
AddWordsToSet (ContentsBufferPntr, ContentsBufferSize,
0 , WordSet);
}
}
if (RecursionLevel + 1 <= MaxRecursionLevel &&
NULL != (ContainerPntr = dynamic_cast<BMailContainer *>(ComponentPntr)))
{
NumComponents = ContainerPntr->CountComponents ();
for (i = 0; i < NumComponents; i++)
{
ComponentPntr = ContainerPntr->GetComponent (i);
ErrorCode = RecursivelyTokenizeMailComponent (ComponentPntr,
OptionalFileName, WordSet, ErrorMessage, RecursionLevel + 1,
MaxRecursionLevel);
if (ErrorCode != B_OK)
break;
}
}
return ErrorCode;
}
void
ABSApp::RefsReceived (BMessage *MessagePntr)
{
if (g_CommanderLooperPntr != NULL)
g_CommanderLooperPntr->CommandReferences (MessagePntr);
}
BHandler * ABSApp::ResolveSpecifier (
BMessage *MessagePntr,
int32 Index,
BMessage *SpecifierMsgPntr,
int32 SpecificationKind,
const char *PropertyPntr)
{
int i;
if (SpecificationKind == B_DIRECT_SPECIFIER)
{
for (i = PN_MAX - 1; i >= 0; i--)
{
if (strcasecmp (PropertyPntr, g_PropertyNames [i]) == 0)
return this;
}
}
return BApplication::ResolveSpecifier (
MessagePntr, Index, SpecifierMsgPntr, SpecificationKind, PropertyPntr);
}
status_t ABSApp::SaveDatabaseIfNeeded (char *ErrorMessage)
{
if (m_DatabaseHasChanged)
return LoadSaveDatabase (false , ErrorMessage);
return B_OK;
}
status_t ABSApp::TokenizeParts (
BPositionIO *PositionIOPntr,
const char *OptionalFileName,
set<string> &WordSet,
char *ErrorMessage)
{
status_t ErrorCode = B_OK;
BEmailMessage WholeEMail;
sprintf (ErrorMessage, "ABSApp::TokenizeParts: While getting e-mail "
"headers, had problems with \"%s\"", OptionalFileName);
ErrorCode = WholeEMail.SetToRFC822 (
PositionIOPntr ,
-1 , true );
if (ErrorCode < 0) goto ErrorExit;
ErrorCode = RecursivelyTokenizeMailComponent (&WholeEMail,
OptionalFileName, WordSet, ErrorMessage, 0 ,
(m_TokenizeMode == TM_JUST_HEADER) ? 0 : 500 );
ErrorExit:
return ErrorCode;
}
status_t ABSApp::TokenizeWhole (
BPositionIO *PositionIOPntr,
const char *OptionalFileName,
set<string> &WordSet,
char *ErrorMessage)
{
string AccumulatedWord;
uint8 Buffer [16 * 1024];
uint8 *BufferCurrentPntr = Buffer + 0;
uint8 *BufferEndPntr = Buffer + 0;
const char *IOErrorString =
"TokenizeWhole: Error %ld while reading \"%s\"";
size_t Length;
int Letter = ' ';
char HexString [4];
int NextLetter = ' ';
int NextNextLetter = ' ';
#define ReadChar(CharVar) \
{ \
if (BufferCurrentPntr < BufferEndPntr) \
CharVar = *BufferCurrentPntr++; \
else \
{ \
ssize_t AmountRead; \
AmountRead = PositionIOPntr->Read (Buffer, sizeof (Buffer)); \
if (AmountRead < 0) \
{ \
sprintf (ErrorMessage, IOErrorString, AmountRead, OptionalFileName); \
return AmountRead; \
} \
else if (AmountRead == 0) \
CharVar = EOF; \
else \
{ \
BufferEndPntr = Buffer + AmountRead; \
BufferCurrentPntr = Buffer + 0; \
CharVar = *BufferCurrentPntr++; \
} \
} \
}
while (true)
{
Letter = NextLetter;
NextLetter = NextNextLetter;
ReadChar (NextNextLetter);
if (Letter == '=')
{
if ((NextLetter == '\r' && NextNextLetter == '\n') ||
(NextLetter == '\n' && NextNextLetter == '\r'))
{
ReadChar (NextLetter);
ReadChar (NextNextLetter);
continue;
}
if (NextLetter == '\n' || NextLetter == '\r')
{
NextLetter = NextNextLetter;
ReadChar (NextNextLetter);
continue;
}
if (NextNextLetter != EOF &&
isxdigit (NextLetter) && isxdigit (NextNextLetter))
{
HexString[0] = NextLetter;
HexString[1] = NextNextLetter;
HexString[2] = 0;
Letter = strtoul (HexString, NULL, 16 );
ReadChar (NextLetter);
ReadChar (NextNextLetter);
}
}
if (Letter >= 'A' && Letter < 'Z')
Letter = Letter + ('a' - 'A');
if (Letter < 0 || (Letter < 128 && g_SpaceCharacters[Letter]))
{
while ((Length = AccumulatedWord.size()) > 0 &&
AccumulatedWord [Length-1] == '.')
AccumulatedWord.resize (Length - 1);
if (Length > 0 && Length <= g_MaxWordLength)
WordSet.insert (AccumulatedWord);
AccumulatedWord.resize (0);
}
else
AccumulatedWord.append (1 , (char) Letter);
if (Letter == EOF)
break;
}
return B_OK;
}
ClassificationChoicesWindow::ClassificationChoicesWindow (
BRect FrameRect,
const char *FileName,
int NumberOfFiles)
: BWindow (FrameRect, "Classification Choices", B_TITLED_WINDOW,
B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS),
m_BulkModeSelectedPntr (NULL),
m_ChoosenClassificationPntr (NULL)
{
ClassificationChoicesView *SubViewPntr;
SubViewPntr = new ClassificationChoicesView (Bounds(),
FileName, NumberOfFiles);
AddChild (SubViewPntr);
SubViewPntr->ResizeToPreferred ();
ResizeTo (SubViewPntr->Frame().Width(), SubViewPntr->Frame().Height());
}
void
ClassificationChoicesWindow::MessageReceived (BMessage *MessagePntr)
{
BControl *ControlPntr;
if (MessagePntr->what >= MSG_CLASS_BUTTONS &&
MessagePntr->what < MSG_CLASS_BUTTONS + CL_MAX)
{
if (m_ChoosenClassificationPntr != NULL)
*m_ChoosenClassificationPntr =
(ClassificationTypes) (MessagePntr->what - MSG_CLASS_BUTTONS);
PostMessage (B_QUIT_REQUESTED);
return;
}
if (MessagePntr->what == MSG_BULK_CHECKBOX)
{
if (m_BulkModeSelectedPntr != NULL &&
MessagePntr->FindPointer ("source", (void **) &ControlPntr) == B_OK)
*m_BulkModeSelectedPntr = (ControlPntr->Value() == B_CONTROL_ON);
return;
}
if (MessagePntr->what == MSG_CANCEL_BUTTON)
{
PostMessage (B_QUIT_REQUESTED);
return;
}
BWindow::MessageReceived (MessagePntr);
}
void
ClassificationChoicesWindow::Go (
bool *BulkModeSelectedPntr,
ClassificationTypes *ChoosenClassificationPntr)
{
status_t ErrorCode = 0;
BView *MainViewPntr;
thread_id WindowThreadID;
m_BulkModeSelectedPntr = BulkModeSelectedPntr;
m_ChoosenClassificationPntr = ChoosenClassificationPntr;
if (m_ChoosenClassificationPntr != NULL)
*m_ChoosenClassificationPntr = CL_MAX;
Show ();
Lock ();
MainViewPntr = FindView ("ClassificationChoicesView");
if (MainViewPntr != NULL)
{
BRect TempRect;
BScreen TempScreen (this);
float X;
float Y;
TempRect = TempScreen.Frame ();
X = TempRect.Width() / 2;
Y = TempRect.Height() / 2;
TempRect = MainViewPntr->Frame();
X -= TempRect.Width() / 2;
Y -= TempRect.Height() / 2;
MoveTo (ceilf (X), ceilf (Y));
}
Unlock ();
WindowThreadID = Thread ();
if (WindowThreadID >= 0)
wait_for_thread (WindowThreadID, &ErrorCode);
}
ClassificationChoicesView::ClassificationChoicesView (
BRect FrameRect,
const char *FileName,
int NumberOfFiles)
: BView (FrameRect, "ClassificationChoicesView",
B_FOLLOW_TOP | B_FOLLOW_LEFT, B_WILL_DRAW | B_NAVIGABLE_JUMP),
m_FileName (FileName),
m_NumberOfFiles (NumberOfFiles),
m_PreferredBottomY (ceilf (g_ButtonHeight * 10))
{
}
void
ClassificationChoicesView::AttachedToWindow ()
{
BButton *ButtonPntr;
BCheckBox *CheckBoxPntr;
ClassificationTypes Classification;
float Margin;
float RowHeight;
float RowTop;
BTextView *TextViewPntr;
BRect TempRect;
char TempString [2048];
BRect TextRect;
float X;
SetViewColor (ui_color (B_PANEL_BACKGROUND_COLOR));
RowHeight = g_ButtonHeight;
if (g_CheckBoxHeight > RowHeight)
RowHeight = g_CheckBoxHeight;
RowHeight = ceilf (RowHeight * 1.1);
TempRect = Bounds ();
RowTop = TempRect.top;
Margin = ceilf ((RowHeight - g_StringViewHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TextRect = TempRect;
TextRect.OffsetTo (0, 0);
TextRect.InsetBy (g_MarginBetweenControls, 2);
sprintf (TempString, "How do you want to classify the file named \"%s\"?",
m_FileName);
TextViewPntr = new BTextView (TempRect, "FileText", TextRect,
B_FOLLOW_TOP | B_FOLLOW_LEFT, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
AddChild (TextViewPntr);
TextViewPntr->SetText (TempString);
TextViewPntr->MakeEditable (false);
TextViewPntr->SetViewColor (ui_color (B_PANEL_BACKGROUND_COLOR));
TextViewPntr->ResizeTo (TempRect.Width (),
3 + TextViewPntr->TextHeight (0, sizeof (TempString)));
RowTop = TextViewPntr->Frame().bottom + Margin;
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
X = Bounds().left + g_MarginBetweenControls;
for (Classification = (ClassificationTypes) 0; Classification < CL_MAX;
Classification = (ClassificationTypes) ((int) Classification + 1))
{
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.left = X;
sprintf (TempString, "%s Button",
g_ClassificationTypeNames [Classification]);
ButtonPntr = new BButton (TempRect, TempString,
g_ClassificationTypeNames [Classification], new BMessage (
ClassificationChoicesWindow::MSG_CLASS_BUTTONS + Classification));
AddChild (ButtonPntr);
ButtonPntr->ResizeToPreferred ();
X = ButtonPntr->Frame().right + 3 * g_MarginBetweenControls;
}
RowTop += ceilf (RowHeight * 1.2);
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.left += g_MarginBetweenControls;
ButtonPntr = new BButton (TempRect, "Cancel Button",
"Cancel", new BMessage (ClassificationChoicesWindow::MSG_CANCEL_BUTTON));
AddChild (ButtonPntr);
ButtonPntr->ResizeToPreferred ();
X = ButtonPntr->Frame().right + g_MarginBetweenControls;
if (m_NumberOfFiles > 1)
{
Margin = ceilf ((RowHeight - g_CheckBoxHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.left = X;
sprintf (TempString, "Mark all %d remaining messages the same way.",
m_NumberOfFiles - 1);
CheckBoxPntr = new BCheckBox (TempRect, "BulkBox", TempString,
new BMessage (ClassificationChoicesWindow::MSG_BULK_CHECKBOX));
AddChild (CheckBoxPntr);
CheckBoxPntr->ResizeToPreferred ();
}
RowTop += RowHeight;
m_PreferredBottomY = RowTop;
}
void
ClassificationChoicesView::GetPreferredSize (float *width, float *height)
{
if (width != NULL)
*width = Bounds().Width();
if (height != NULL)
*height = m_PreferredBottomY;
}
CommanderLooper::CommanderLooper ()
: BLooper ("CommanderLooper", B_NORMAL_PRIORITY),
m_IsBusy (false)
{
}
CommanderLooper::~CommanderLooper ()
{
g_CommanderLooperPntr = NULL;
delete g_CommanderMessenger;
g_CommanderMessenger = NULL;
}
void
CommanderLooper::CommandArguments (int argc, char **argv)
{
int i;
BMessage InternalMessage;
InternalMessage.what = MSG_COMMAND_ARGUMENTS;
for (i = 0; i < argc; i++)
InternalMessage.AddString ("arg", argv[i]);
PostMessage (&InternalMessage);
}
void
CommanderLooper::CommandReferences (
BMessage *MessagePntr,
bool BulkMode,
ClassificationTypes BulkClassification)
{
entry_ref EntryRef;
int i;
BMessage InternalMessage;
InternalMessage.what = MSG_COMMAND_FILE_REFS;
for (i = 0; MessagePntr->FindRef ("refs", i, &EntryRef) == B_OK; i++)
InternalMessage.AddRef ("refs", &EntryRef);
InternalMessage.AddBool ("BulkMode", BulkMode);
InternalMessage.AddInt32 ("BulkClassification", BulkClassification);
PostMessage (&InternalMessage);
}
bool
CommanderLooper::IsBusy ()
{
if (m_IsBusy)
return true;
if (IsLocked () || !MessageQueue()->IsEmpty ())
return true;
return false;
}
void
CommanderLooper::MessageReceived (BMessage *MessagePntr)
{
m_IsBusy = true;
if (MessagePntr->what == MSG_COMMAND_ARGUMENTS)
ProcessArgs (MessagePntr);
else if (MessagePntr->what == MSG_COMMAND_FILE_REFS)
ProcessRefs (MessagePntr);
else
BLooper::MessageReceived (MessagePntr);
m_IsBusy = false;
}
void
CommanderLooper::ProcessArgs (BMessage *MessagePntr)
{
int32 argc = 0;
const char **argv = NULL;
int ArgumentIndex;
uint32 CommandCode;
const char *CommandWord;
status_t ErrorCode;
const char *ErrorTitle = "ProcessArgs";
char *EndPntr;
int32 i;
BMessage ReplyMessage;
BMessage ScriptMessage;
struct property_info *PropInfoPntr;
const char *PropertyName;
bool TempBool;
float TempFloat;
int32 TempInt32;
const char *TempStringPntr;
type_code TypeCode;
const char *ValuePntr;
ErrorCode = MessagePntr->GetInfo ("arg", &TypeCode, &argc);
if (ErrorCode != B_OK || TypeCode != B_STRING_TYPE)
{
DisplayErrorMessage ("Unable to find argument strings in message",
ErrorCode, ErrorTitle);
goto ErrorExit;
}
if (argc < 2)
{
cerr << PrintUsage;
DisplayErrorMessage ("You need to specify a command word, like GET, SET "
"and so on followed by a property, like DatabaseFile, and maybe "
"followed by a value of some sort", -1, ErrorTitle);
goto ErrorExit;
}
argv = (const char **) malloc (sizeof (char *) * argc);
if (argv == NULL)
{
DisplayErrorMessage ("Out of memory when allocating argv array",
ENOMEM, ErrorTitle);
goto ErrorExit;
}
for (i = 0; i < argc; i++)
{
if ((ErrorCode = MessagePntr->FindString ("arg", i, &argv[i])) != B_OK)
{
DisplayErrorMessage ("Unable to find argument in the BMessage",
ErrorCode, ErrorTitle);
goto ErrorExit;
}
}
CommandWord = argv[1];
if (strcasecmp (CommandWord, "quit") == 0)
{
g_QuitCountdown = 10;
goto ErrorExit;
}
if (strcasecmp (CommandWord, "set") == 0)
CommandCode = B_SET_PROPERTY;
else if (strcasecmp (CommandWord, "get") == 0)
CommandCode = B_GET_PROPERTY;
else if (strcasecmp (CommandWord, "count") == 0)
CommandCode = B_COUNT_PROPERTIES;
else if (strcasecmp (CommandWord, "create") == 0)
CommandCode = B_CREATE_PROPERTY;
else if (strcasecmp (CommandWord, "delete") == 0)
CommandCode = B_DELETE_PROPERTY;
else
CommandCode = B_EXECUTE_PROPERTY;
if (CommandCode == B_EXECUTE_PROPERTY)
{
PropertyName = CommandWord;
ArgumentIndex = 2;
}
else
{
if (CommandCode == B_SET_PROPERTY)
{
if (argc < 4)
{
cerr << PrintUsage;
DisplayErrorMessage ("SET commands require at least one "
"argument value after the property name", -1, ErrorTitle);
goto ErrorExit;
}
}
else
if (argc < 3)
{
cerr << PrintUsage;
DisplayErrorMessage ("You need to specify a property to act on",
-1, ErrorTitle);
goto ErrorExit;
}
PropertyName = argv[2];
ArgumentIndex = 3;
}
for (PropInfoPntr = g_ScriptingPropertyList + 0; true; PropInfoPntr++)
{
if (PropInfoPntr->name == 0)
{
cerr << PrintUsage;
DisplayErrorMessage ("The property specified isn't known or "
"doesn't support the requested action (usually means it is an "
"unknown command)", -1, ErrorTitle);
goto ErrorExit;
}
if (PropInfoPntr->commands[0] == CommandCode &&
strcasecmp (PropertyName, PropInfoPntr->name) == 0)
break;
}
ScriptMessage.MakeEmpty ();
ScriptMessage.what = CommandCode;
ScriptMessage.AddSpecifier (PropertyName);
while (true)
{
if (ArgumentIndex < argc)
{
ValuePntr = argv[ArgumentIndex];
if (strcasecmp (ValuePntr, "yes") == 0 ||
strcasecmp (ValuePntr, "true") == 0)
ScriptMessage.AddBool (g_DataName, true);
else if (strcasecmp (ValuePntr, "no") == 0 ||
strcasecmp (ValuePntr, "false") == 0)
ScriptMessage.AddBool (g_DataName, false);
else
{
i = strtol (ValuePntr, &EndPntr, 0);
if (*EndPntr == 0)
ScriptMessage.AddInt32 (g_DataName, i);
else
ScriptMessage.AddString (g_DataName, ValuePntr);
}
}
ErrorCode = be_app_messenger.SendMessage (&ScriptMessage, &ReplyMessage);
if (ErrorCode != B_OK)
{
DisplayErrorMessage ("Unable to send scripting command",
ErrorCode, ErrorTitle);
goto ErrorExit;
}
if (ReplyMessage.FindString ("CommandText", &TempStringPntr) == B_OK)
{
TempInt32 = -1;
if (ReplyMessage.FindInt32 ("error", &TempInt32) == B_OK &&
TempInt32 == B_OK)
{
cout << "Result of command to " << TempStringPntr << " is:\t";
if (ReplyMessage.FindString (g_ResultName, &TempStringPntr) == B_OK)
cout << "\"" << TempStringPntr << "\"";
else if (ReplyMessage.FindInt32 (g_ResultName, &TempInt32) == B_OK)
cout << TempInt32;
else if (ReplyMessage.FindFloat (g_ResultName, &TempFloat) == B_OK)
cout << TempFloat;
else if (ReplyMessage.FindBool (g_ResultName, &TempBool) == B_OK)
cout << (TempBool ? "true" : "false");
else
cout << "just plain success";
if (ReplyMessage.FindInt32 ("count", &TempInt32) == B_OK)
cout << "\t(count " << TempInt32 << ")";
for (i = 0; (i < 50) &&
ReplyMessage.FindString ("words", i, &TempStringPntr) == B_OK &&
ReplyMessage.FindFloat ("ratios", i, &TempFloat) == B_OK;
i++)
{
if (i == 0)
cout << "\twith top words:\t";
else
cout << "\t";
cout << TempStringPntr << "/" << TempFloat;
}
cout << endl;
}
else
{
cout << "Failure of command " << TempStringPntr << ", error ";
cout << TempInt32 << " (" << strerror (TempInt32) << ")";
if (ReplyMessage.FindString ("message", &TempStringPntr) == B_OK)
cout << ", message: " << TempStringPntr;
cout << "." << endl;
}
}
ScriptMessage.RemoveName (g_DataName);
if (++ArgumentIndex >= argc)
break;
}
ErrorExit:
free (argv);
}
void
CommanderLooper::ProcessRefs (BMessage *MessagePntr)
{
bool BulkMode = false;
ClassificationTypes BulkClassification = CL_GENUINE;
ClassificationChoicesWindow *ChoiceWindowPntr;
BEntry Entry;
entry_ref EntryRef;
status_t ErrorCode;
const char *ErrorTitle = "CommanderLooper::ProcessRefs";
int32 NumberOfRefs = 0;
BPath Path;
int RefIndex;
BMessage ReplyMessage;
BMessage ScriptingMessage;
bool TempBool;
BFile TempFile;
int32 TempInt32;
char TempString [PATH_MAX + 1024];
type_code TypeCode;
TempInt32 = 0;
while (!g_AppReadyToRunCompleted && TempInt32++ < 10)
snooze (200000);
ErrorCode = MessagePntr->GetInfo ("refs", &TypeCode, &NumberOfRefs);
if (ErrorCode != B_OK || TypeCode != B_REF_TYPE || NumberOfRefs <= 0)
{
DisplayErrorMessage ("Unable to get refs from the message",
ErrorCode, ErrorTitle);
return;
}
if (MessagePntr->FindBool ("BulkMode", &TempBool) == B_OK)
BulkMode = TempBool;
if (MessagePntr->FindInt32 ("BulkClassification", &TempInt32) == B_OK &&
TempInt32 >= 0 && TempInt32 < CL_MAX)
BulkClassification = (ClassificationTypes) TempInt32;
for (RefIndex = 0;
MessagePntr->FindRef ("refs", RefIndex, &EntryRef) == B_OK;
RefIndex++)
{
ScriptingMessage.MakeEmpty ();
ScriptingMessage.what = 0;
ErrorCode = Entry.SetTo (&EntryRef, true );
if (ErrorCode != B_OK ||
((ErrorCode = B_ENTRY_NOT_FOUND) != 0
&& !Entry.Exists ()) ||
((ErrorCode = Entry.GetPath (&Path)) != B_OK))
{
DisplayErrorMessage ("Bad entry reference encountered, will skip it",
ErrorCode, ErrorTitle);
BulkMode = false;
continue;
}
if (Entry.IsFile ())
{
ErrorCode = TempFile.SetTo (&Entry, B_READ_ONLY);
if (ErrorCode != B_OK)
{
sprintf (TempString, "Unable to open file \"%s\" for reading, will "
"skip it", Path.Path ());
DisplayErrorMessage (TempString, ErrorCode, ErrorTitle);
BulkMode = false;
continue;
}
if (TempFile.Read (TempString, strlen (g_DatabaseRecognitionString)) ==
(int) strlen (g_DatabaseRecognitionString) && strncmp (TempString,
g_DatabaseRecognitionString, strlen (g_DatabaseRecognitionString)) == 0)
{
ScriptingMessage.what = B_SET_PROPERTY;
ScriptingMessage.AddSpecifier (g_PropertyNames[PN_DATABASE_FILE]);
ScriptingMessage.AddString (g_DataName, Path.Path ());
}
TempFile.Unset ();
}
if (ScriptingMessage.what == 0)
{
if (!Entry.IsFile ())
{
sprintf (TempString, "\"%s\" is not a file, can't do anything with it",
Path.Path ());
DisplayErrorMessage (TempString, -1, ErrorTitle);
BulkMode = false;
continue;
}
if (!BulkMode)
{
ChoiceWindowPntr = new ClassificationChoicesWindow (
BRect (40, 40, 40 + 50 * g_MarginBetweenControls,
40 + g_ButtonHeight * 5), Path.Path (), NumberOfRefs - RefIndex);
ChoiceWindowPntr->Go (&BulkMode, &BulkClassification);
if (BulkClassification == CL_MAX)
break;
}
ScriptingMessage.what = B_SET_PROPERTY;
if (BulkClassification == CL_GENUINE)
ScriptingMessage.AddSpecifier (g_PropertyNames[PN_GENUINE]);
else if (BulkClassification == CL_SPAM)
ScriptingMessage.AddSpecifier (g_PropertyNames[PN_SPAM]);
else if (BulkClassification == CL_UNCERTAIN)
ScriptingMessage.AddSpecifier (g_PropertyNames[PN_UNCERTAIN]);
else
break;
ScriptingMessage.AddString (g_DataName, Path.Path ());
}
ErrorCode =
be_app_messenger.SendMessage (&ScriptingMessage, &ReplyMessage);
if (ErrorCode != B_OK)
{
DisplayErrorMessage ("Unable to send scripting command",
ErrorCode, ErrorTitle);
return;
}
if (ReplyMessage.FindInt32 ("error", &TempInt32) != B_OK ||
TempInt32 != B_OK)
BulkMode = false;
}
}
ControlsView::ControlsView (BRect NewBounds)
: BView (NewBounds, "ControlsView", B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT,
B_WILL_DRAW | B_PULSE_NEEDED | B_NAVIGABLE_JUMP | B_FRAME_EVENTS),
m_AboutButtonPntr (NULL),
m_AddExampleButtonPntr (NULL),
m_BrowseButtonPntr (NULL),
m_BrowseFilePanelPntr (NULL),
m_CreateDatabaseButtonPntr (NULL),
m_DatabaseFileNameTextboxPntr (NULL),
m_DatabaseLoadDone (false),
m_EstimateSpamButtonPntr (NULL),
m_EstimateSpamFilePanelPntr (NULL),
m_GenuineCountTextboxPntr (NULL),
m_IgnorePreviousClassCheckboxPntr (NULL),
m_InstallThingsButtonPntr (NULL),
m_PurgeAgeTextboxPntr (NULL),
m_PurgeButtonPntr (NULL),
m_PurgePopularityTextboxPntr (NULL),
m_ResetToDefaultsButtonPntr (NULL),
m_ScoringModeMenuBarPntr (NULL),
m_ScoringModePopUpMenuPntr (NULL),
m_ServerModeCheckboxPntr (NULL),
m_SpamCountTextboxPntr (NULL),
m_TimeOfLastPoll (0),
m_TokenizeModeMenuBarPntr (NULL),
m_TokenizeModePopUpMenuPntr (NULL),
m_WordCountTextboxPntr (NULL)
{
}
ControlsView::~ControlsView ()
{
if (m_BrowseFilePanelPntr != NULL)
{
delete m_BrowseFilePanelPntr;
m_BrowseFilePanelPntr = NULL;
}
if (m_EstimateSpamFilePanelPntr != NULL)
{
delete m_EstimateSpamFilePanelPntr;
m_EstimateSpamFilePanelPntr = NULL;
}
}
void
ControlsView::AttachedToWindow ()
{
float BigPurgeButtonTop;
BMessage CommandMessage;
const char *EightDigitsString = " 12345678 ";
float Height;
float Margin;
float RowHeight;
float RowTop;
ScoringModes ScoringMode;
const char *StringPntr;
BMenuItem *TempMenuItemPntr;
BRect TempRect;
char TempString [PATH_MAX];
TokenizeModes TokenizeMode;
float Width;
float X;
SetViewColor (ui_color (B_PANEL_BACKGROUND_COLOR));
TempRect = Bounds ();
X = TempRect.right;
RowTop = TempRect.top;
RowHeight = g_ButtonHeight;
if (g_TextBoxHeight > RowHeight)
RowHeight = g_TextBoxHeight;
RowHeight = ceilf (RowHeight * 1.1);
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_ButtonHeight;
CommandMessage.MakeEmpty ();
CommandMessage.what = B_CREATE_PROPERTY;
CommandMessage.AddSpecifier (g_PropertyNames[PN_DATABASE_FILE]);
m_CreateDatabaseButtonPntr = new BButton (TempRect, "Create Button",
"Create", new BMessage (CommandMessage), B_FOLLOW_RIGHT | B_FOLLOW_TOP);
if (m_CreateDatabaseButtonPntr == NULL) goto ErrorExit;
AddChild (m_CreateDatabaseButtonPntr);
m_CreateDatabaseButtonPntr->SetTarget (be_app);
m_CreateDatabaseButtonPntr->ResizeToPreferred ();
m_CreateDatabaseButtonPntr->GetPreferredSize (&Width, &Height);
m_CreateDatabaseButtonPntr->MoveTo (X - Width, TempRect.top);
X -= Width + g_MarginBetweenControls;
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_ButtonHeight;
m_BrowseButtonPntr = new BButton (TempRect, "Browse Button",
"Browse…", new BMessage (MSG_BROWSE_BUTTON), B_FOLLOW_RIGHT | B_FOLLOW_TOP);
if (m_BrowseButtonPntr == NULL) goto ErrorExit;
AddChild (m_BrowseButtonPntr);
m_BrowseButtonPntr->SetTarget (this);
m_BrowseButtonPntr->ResizeToPreferred ();
m_BrowseButtonPntr->GetPreferredSize (&Width, &Height);
m_BrowseButtonPntr->MoveTo (X - Width, TempRect.top);
X -= Width + g_MarginBetweenControls;
Margin = ceilf ((RowHeight - g_TextBoxHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_TextBoxHeight;
TempRect.right = X;
StringPntr = "Word Database:";
strcpy (m_DatabaseFileNameCachedValue, "Unknown...");
m_DatabaseFileNameTextboxPntr = new BTextControl (TempRect,
"File Name",
StringPntr ,
m_DatabaseFileNameCachedValue ,
new BMessage (MSG_DATABASE_NAME),
B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP);
AddChild (m_DatabaseFileNameTextboxPntr);
m_DatabaseFileNameTextboxPntr->SetTarget (this);
m_DatabaseFileNameTextboxPntr->SetDivider (
be_plain_font->StringWidth (StringPntr) + g_MarginBetweenControls);
RowTop += RowHeight ;
BigPurgeButtonTop = RowTop;
TempRect = Bounds ();
X = TempRect.left;
RowHeight = g_TextBoxHeight;
RowHeight = ceilf (RowHeight * 1.1);
StringPntr = "Number of occurrences needed to store a word:";
m_PurgeAgeCachedValue = 12345678;
Margin = ceilf ((RowHeight - g_TextBoxHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_TextBoxHeight;
TempRect.left = X;
TempRect.right = TempRect.left +
be_plain_font->StringWidth (StringPntr) +
be_plain_font->StringWidth (EightDigitsString) +
3 * g_MarginBetweenControls;
sprintf (TempString, "%d", (int) m_PurgeAgeCachedValue);
m_PurgeAgeTextboxPntr = new BTextControl (TempRect,
"Purge Age",
StringPntr ,
TempString ,
new BMessage (MSG_PURGE_AGE),
B_FOLLOW_LEFT | B_FOLLOW_TOP,
B_WILL_DRAW | B_NAVIGABLE);
AddChild (m_PurgeAgeTextboxPntr);
m_PurgeAgeTextboxPntr->SetTarget (this);
m_PurgeAgeTextboxPntr->SetDivider (
be_plain_font->StringWidth (StringPntr) + g_MarginBetweenControls);
RowTop += RowHeight ;
TempRect = Bounds ();
X = TempRect.left;
RowHeight = g_TextBoxHeight;
RowHeight = ceilf (RowHeight * 1.1);
StringPntr = "Number of messages to store words from:";
m_PurgePopularityCachedValue = 87654321;
Margin = ceilf ((RowHeight - g_TextBoxHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_TextBoxHeight;
TempRect.left = X;
TempRect.right = TempRect.left +
be_plain_font->StringWidth (StringPntr) +
be_plain_font->StringWidth (EightDigitsString) +
3 * g_MarginBetweenControls;
X = TempRect.right + g_MarginBetweenControls;
sprintf (TempString, "%d", (int) m_PurgePopularityCachedValue);
m_PurgePopularityTextboxPntr = new BTextControl (TempRect,
"Purge Popularity",
StringPntr ,
TempString ,
new BMessage (MSG_PURGE_POPULARITY),
B_FOLLOW_LEFT | B_FOLLOW_TOP,
B_WILL_DRAW | B_NAVIGABLE);
AddChild (m_PurgePopularityTextboxPntr);
m_PurgePopularityTextboxPntr->SetTarget (this);
m_PurgePopularityTextboxPntr->SetDivider (
be_plain_font->StringWidth (StringPntr) + g_MarginBetweenControls);
StringPntr = "Remove Old Words";
Margin = ceilf ((((RowTop + RowHeight) - BigPurgeButtonTop) -
2 * g_TextBoxHeight) / 2);
TempRect.top = BigPurgeButtonTop + Margin;
TempRect.bottom = TempRect.top + 2 * g_TextBoxHeight;
TempRect.left = X;
TempRect.right = X + ceilf (2 * be_plain_font->StringWidth (StringPntr));
CommandMessage.MakeEmpty ();
CommandMessage.what = B_EXECUTE_PROPERTY;
CommandMessage.AddSpecifier (g_PropertyNames[PN_PURGE]);
m_PurgeButtonPntr = new BButton (TempRect, "Purge Button",
StringPntr, new BMessage (CommandMessage), B_FOLLOW_LEFT | B_FOLLOW_TOP);
if (m_PurgeButtonPntr == NULL) goto ErrorExit;
m_PurgeButtonPntr->ResizeToPreferred();
AddChild (m_PurgeButtonPntr);
m_PurgeButtonPntr->SetTarget (be_app);
RowTop += RowHeight ;
TempRect = Bounds ();
X = TempRect.left;
RowHeight = g_CheckBoxHeight;
RowHeight = ceilf (RowHeight * 1.1);
StringPntr = "Allow Retraining on a Message";
m_IgnorePreviousClassCachedValue = false;
Margin = ceilf ((RowHeight - g_CheckBoxHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_CheckBoxHeight;
TempRect.left = X;
m_IgnorePreviousClassCheckboxPntr = new BCheckBox (TempRect,
"Ignore Check",
StringPntr,
new BMessage (MSG_IGNORE_CLASSIFICATION),
B_FOLLOW_TOP | B_FOLLOW_LEFT);
if (m_IgnorePreviousClassCheckboxPntr == NULL) goto ErrorExit;
AddChild (m_IgnorePreviousClassCheckboxPntr);
m_IgnorePreviousClassCheckboxPntr->SetTarget (this);
m_IgnorePreviousClassCheckboxPntr->ResizeToPreferred ();
m_IgnorePreviousClassCheckboxPntr->GetPreferredSize (&Width, &Height);
X += Width + g_MarginBetweenControls;
RowTop += RowHeight ;
TempRect = Bounds ();
RowHeight = g_CheckBoxHeight;
RowHeight = ceilf (RowHeight * 1.1);
StringPntr = "Print errors to Terminal";
m_ServerModeCachedValue = false;
Margin = ceilf ((RowHeight - g_CheckBoxHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_CheckBoxHeight;
m_ServerModeCheckboxPntr = new BCheckBox (TempRect,
"ServerMode Check",
StringPntr,
new BMessage (MSG_SERVER_MODE),
B_FOLLOW_TOP | B_FOLLOW_LEFT);
if (m_ServerModeCheckboxPntr == NULL) goto ErrorExit;
AddChild (m_ServerModeCheckboxPntr);
m_ServerModeCheckboxPntr->SetTarget (this);
m_ServerModeCheckboxPntr->ResizeToPreferred ();
m_ServerModeCheckboxPntr->GetPreferredSize (&Width, &Height);
RowTop += RowHeight ;
TempRect = Bounds ();
RowHeight = g_PopUpMenuHeight;
RowHeight = ceilf (RowHeight * 1.1);
Margin = ceilf ((RowHeight - g_PopUpMenuHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_PopUpMenuHeight;
m_TokenizeModeCachedValue = TM_MAX;
m_TokenizeModeMenuBarPntr = new BMenuBar (TempRect, "TokenizeModeMenuBar",
B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, B_ITEMS_IN_COLUMN,
false );
if (m_TokenizeModeMenuBarPntr == NULL) goto ErrorExit;
m_TokenizeModePopUpMenuPntr = new BPopUpMenu ("TokenizeModePopUpMenu");
if (m_TokenizeModePopUpMenuPntr == NULL) goto ErrorExit;
for (TokenizeMode = (TokenizeModes) 0;
TokenizeMode < TM_MAX;
TokenizeMode = (TokenizeModes) ((int) TokenizeMode + 1))
{
CommandMessage.MakeEmpty ();
CommandMessage.what = B_SET_PROPERTY;
CommandMessage.AddSpecifier (g_PropertyNames[PN_TOKENIZE_MODE]);
CommandMessage.AddString (g_DataName, g_TokenizeModeNames[TokenizeMode]);
strcpy (TempString, g_TokenizeModeNames[TokenizeMode]);
switch (TokenizeMode)
{
case TM_WHOLE:
strcat (TempString, " - Scan everything");
break;
case TM_PLAIN_TEXT:
strcat (TempString, " - Scan e-mail body text except rich text");
break;
case TM_PLAIN_TEXT_HEADER:
strcat (TempString, " - Scan entire e-mail text except rich text");
break;
case TM_ANY_TEXT:
strcat (TempString, " - Scan e-mail body text and text attachments");
break;
case TM_ANY_TEXT_HEADER:
strcat (TempString, " - Scan entire e-mail text and text attachments (recommended)");
break;
case TM_ALL_PARTS:
strcat (TempString, " - Scan e-mail body and all attachments");
break;
case TM_ALL_PARTS_HEADER:
strcat (TempString, " - Scan all parts of the e-mail");
break;
case TM_JUST_HEADER:
strcat (TempString, " - Scan just the header (mail routing information)");
break;
default:
break;
}
TempMenuItemPntr =
new BMenuItem (TempString, new BMessage (CommandMessage));
if (TempMenuItemPntr == NULL) goto ErrorExit;
TempMenuItemPntr->SetTarget (be_app);
m_TokenizeModePopUpMenuPntr->AddItem (TempMenuItemPntr);
}
m_TokenizeModeMenuBarPntr->AddItem (m_TokenizeModePopUpMenuPntr);
AddChild (m_TokenizeModeMenuBarPntr);
RowTop += RowHeight ;
TempRect = Bounds ();
RowHeight = g_PopUpMenuHeight;
RowHeight = ceilf (RowHeight * 1.1);
Margin = ceilf ((RowHeight - g_PopUpMenuHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_PopUpMenuHeight;
m_ScoringModeCachedValue = SM_MAX;
m_ScoringModeMenuBarPntr = new BMenuBar (TempRect, "ScoringModeMenuBar",
B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, B_ITEMS_IN_COLUMN,
false );
if (m_ScoringModeMenuBarPntr == NULL) goto ErrorExit;
m_ScoringModePopUpMenuPntr = new BPopUpMenu ("ScoringModePopUpMenu");
if (m_ScoringModePopUpMenuPntr == NULL) goto ErrorExit;
for (ScoringMode = (ScoringModes) 0;
ScoringMode < SM_MAX;
ScoringMode = (ScoringModes) ((int) ScoringMode + 1))
{
CommandMessage.MakeEmpty ();
CommandMessage.what = B_SET_PROPERTY;
CommandMessage.AddSpecifier (g_PropertyNames[PN_SCORING_MODE]);
CommandMessage.AddString (g_DataName, g_ScoringModeNames[ScoringMode]);
switch (ScoringMode)
{
case SM_ROBINSON:
strcpy (TempString, "Learning method 1: Naive Bayesian");
break;
case SM_CHISQUARED:
strcpy (TempString, "Learning method 2: Chi-Squared");
break;
default:
break;
}
TempMenuItemPntr =
new BMenuItem (TempString, new BMessage (CommandMessage));
if (TempMenuItemPntr == NULL) goto ErrorExit;
TempMenuItemPntr->SetTarget (be_app);
m_ScoringModePopUpMenuPntr->AddItem (TempMenuItemPntr);
}
m_ScoringModeMenuBarPntr->AddItem (m_ScoringModePopUpMenuPntr);
AddChild (m_ScoringModeMenuBarPntr);
RowTop += RowHeight ;
TempRect = Bounds ();
RowHeight = g_ButtonHeight;
RowHeight = ceilf (RowHeight * 1.1);
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_ButtonHeight;
CommandMessage.MakeEmpty ();
CommandMessage.what = B_EXECUTE_PROPERTY;
CommandMessage.AddSpecifier (g_PropertyNames[PN_INSTALL_THINGS]);
m_InstallThingsButtonPntr = new BButton (TempRect, "Install Button",
"Install spam types",
new BMessage (CommandMessage),
B_FOLLOW_LEFT | B_FOLLOW_TOP);
if (m_InstallThingsButtonPntr == NULL) goto ErrorExit;
AddChild (m_InstallThingsButtonPntr);
m_InstallThingsButtonPntr->SetTarget (be_app);
m_InstallThingsButtonPntr->ResizeToPreferred ();
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_ButtonHeight;
CommandMessage.MakeEmpty ();
CommandMessage.what = B_EXECUTE_PROPERTY;
CommandMessage.AddSpecifier (g_PropertyNames[PN_RESET_TO_DEFAULTS]);
m_ResetToDefaultsButtonPntr = new BButton (TempRect, "Reset Button",
"Default settings", new BMessage (CommandMessage),
B_FOLLOW_RIGHT | B_FOLLOW_TOP);
if (m_ResetToDefaultsButtonPntr == NULL) goto ErrorExit;
AddChild (m_ResetToDefaultsButtonPntr);
m_ResetToDefaultsButtonPntr->SetTarget (be_app);
m_ResetToDefaultsButtonPntr->ResizeToPreferred ();
m_ResetToDefaultsButtonPntr->GetPreferredSize (&Width, &Height);
m_ResetToDefaultsButtonPntr->MoveTo (TempRect.right - Width, TempRect.top);
RowTop += RowHeight ;
TempRect = Bounds ();
X = TempRect.left;
RowHeight = g_ButtonHeight;
RowHeight = ceilf (RowHeight * 1.1);
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_ButtonHeight;
TempRect.left = X;
m_EstimateSpamButtonPntr = new BButton (TempRect, "Estimate Button",
"Scan a message",
new BMessage (MSG_ESTIMATE_BUTTON),
B_FOLLOW_LEFT | B_FOLLOW_TOP);
if (m_EstimateSpamButtonPntr == NULL) goto ErrorExit;
AddChild (m_EstimateSpamButtonPntr);
m_EstimateSpamButtonPntr->SetTarget (this);
m_EstimateSpamButtonPntr->ResizeToPreferred ();
X = m_EstimateSpamButtonPntr->Frame().right + g_MarginBetweenControls;
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_ButtonHeight;
TempRect.left = X;
m_AddExampleButtonPntr = new BButton (TempRect, "Example Button",
"Train spam filter on a message",
new BMessage (MSG_BROWSE_BUTTON),
B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
B_WILL_DRAW | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE);
if (m_AddExampleButtonPntr == NULL) goto ErrorExit;
AddChild (m_AddExampleButtonPntr);
m_AddExampleButtonPntr->SetTarget (this);
m_AddExampleButtonPntr->ResizeToPreferred ();
X = m_AddExampleButtonPntr->Frame().right + g_MarginBetweenControls;
Margin = ceilf ((RowHeight - g_ButtonHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_ButtonHeight;
TempRect.left = X;
m_AboutButtonPntr = new BButton (TempRect, "About Button",
"About…",
new BMessage (B_ABOUT_REQUESTED),
B_FOLLOW_RIGHT | B_FOLLOW_TOP);
if (m_AboutButtonPntr == NULL) goto ErrorExit;
AddChild (m_AboutButtonPntr);
m_AboutButtonPntr->SetTarget (be_app);
RowTop += RowHeight ;
TempRect = Bounds ();
RowHeight = g_TextBoxHeight;
RowHeight = ceilf (RowHeight * 1.1);
StringPntr = "Genuine messages:";
m_GenuineCountCachedValue = 87654321;
sprintf (TempString, "%d", (int) m_GenuineCountCachedValue);
Margin = ceilf ((RowHeight - g_TextBoxHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_TextBoxHeight;
TempRect.right = TempRect.left +
be_plain_font->StringWidth (StringPntr) +
be_plain_font->StringWidth (TempString) +
3 * g_MarginBetweenControls;
m_GenuineCountTextboxPntr = new BTextControl (TempRect,
"Genuine count",
StringPntr ,
TempString ,
NULL ,
B_FOLLOW_LEFT | B_FOLLOW_TOP,
B_WILL_DRAW );
AddChild (m_GenuineCountTextboxPntr);
m_GenuineCountTextboxPntr->SetTarget (this);
m_GenuineCountTextboxPntr->SetDivider (
be_plain_font->StringWidth (StringPntr) + g_MarginBetweenControls);
m_GenuineCountTextboxPntr->SetEnabled (false);
StringPntr = "Word count:";
m_WordCountCachedValue = 87654321;
sprintf (TempString, "%d", (int) m_WordCountCachedValue);
Margin = ceilf ((RowHeight - g_TextBoxHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_TextBoxHeight;
Width = be_plain_font->StringWidth (StringPntr) +
be_plain_font->StringWidth (TempString) +
3 * g_MarginBetweenControls;
TempRect.left = ceilf ((TempRect.right - TempRect.left) / 2 - Width / 2);
TempRect.right = TempRect.left + Width;
m_WordCountTextboxPntr = new BTextControl (TempRect,
"Word count",
StringPntr ,
TempString ,
NULL ,
B_FOLLOW_H_CENTER | B_FOLLOW_TOP,
B_WILL_DRAW );
AddChild (m_WordCountTextboxPntr);
m_WordCountTextboxPntr->SetTarget (this);
m_WordCountTextboxPntr->SetDivider (
be_plain_font->StringWidth (StringPntr) + g_MarginBetweenControls);
m_WordCountTextboxPntr->SetEnabled (false);
StringPntr = "Spam messages:";
m_SpamCountCachedValue = 87654321;
sprintf (TempString, "%d", (int) m_SpamCountCachedValue);
Margin = ceilf ((RowHeight - g_TextBoxHeight) / 2);
TempRect = Bounds ();
TempRect.top = RowTop + Margin;
TempRect.bottom = TempRect.top + g_TextBoxHeight;
TempRect.left = TempRect.right -
be_plain_font->StringWidth (StringPntr) -
be_plain_font->StringWidth (TempString) -
3 * g_MarginBetweenControls;
m_SpamCountTextboxPntr = new BTextControl (TempRect,
"Spam count",
StringPntr ,
TempString ,
NULL ,
B_FOLLOW_RIGHT | B_FOLLOW_TOP,
B_WILL_DRAW );
AddChild (m_SpamCountTextboxPntr);
m_SpamCountTextboxPntr->SetTarget (this);
m_SpamCountTextboxPntr->SetDivider (
be_plain_font->StringWidth (StringPntr) + g_MarginBetweenControls);
m_SpamCountTextboxPntr->SetEnabled (false);
RowTop += RowHeight ;
ResizeTo (Bounds().Width(), RowTop - Bounds().top + 1);
return;
ErrorExit:
DisplayErrorMessage ("Unable to initialise the controls view.");
}
void
ControlsView::BrowseForDatabaseFile ()
{
if (m_BrowseFilePanelPntr == NULL)
{
BEntry DirectoryEntry;
entry_ref DirectoryEntryRef;
BMessage GetDatabasePathCommand;
BMessage GetDatabasePathResult;
const char *StringPntr = NULL;
GetDatabasePathCommand.what = B_GET_PROPERTY;
GetDatabasePathCommand.AddSpecifier (g_PropertyNames[PN_DATABASE_FILE]);
be_app_messenger.SendMessage (&GetDatabasePathCommand,
&GetDatabasePathResult, 5000000 ,
5000000 );
if (GetDatabasePathResult.FindString (g_ResultName, &StringPntr) != B_OK ||
DirectoryEntry.SetTo (StringPntr) != B_OK ||
DirectoryEntry.GetParent (&DirectoryEntry) != B_OK)
DirectoryEntry.SetTo (".");
if (DirectoryEntry.GetRef (&DirectoryEntryRef) != B_OK)
{
DisplayErrorMessage (
"Unable to set up the file requestor starting directory. Sorry.");
return;
}
m_BrowseFilePanelPntr = new BFilePanel (
B_OPEN_PANEL ,
&be_app_messenger ,
&DirectoryEntryRef ,
B_FILE_NODE,
true ,
NULL ,
NULL ,
false ,
true );
}
if (m_BrowseFilePanelPntr != NULL)
m_BrowseFilePanelPntr->Show ();
}
void
ControlsView::BrowseForFileToEstimate ()
{
if (m_EstimateSpamFilePanelPntr == NULL)
{
BEntry DirectoryEntry;
entry_ref DirectoryEntryRef;
status_t ErrorCode;
BMessenger MessengerToSelf (this);
BPath PathToMailDirectory;
ErrorCode = find_directory (B_USER_DIRECTORY, &PathToMailDirectory);
if (ErrorCode == B_OK)
{
PathToMailDirectory.Append ("mail");
ErrorCode = DirectoryEntry.SetTo (PathToMailDirectory.Path(),
true );
if (ErrorCode != B_OK || !DirectoryEntry.Exists ())
{
find_directory (B_USER_DIRECTORY, &PathToMailDirectory);
ErrorCode = DirectoryEntry.SetTo (PathToMailDirectory.Path(), true);
}
}
if (ErrorCode != B_OK)
PathToMailDirectory.SetTo (".");
DirectoryEntry.SetTo (PathToMailDirectory.Path(), true);
if (DirectoryEntry.GetRef (&DirectoryEntryRef) != B_OK)
{
DisplayErrorMessage (
"Unable to set up the file requestor starting directory. Sorry.");
return;
}
m_EstimateSpamFilePanelPntr = new BFilePanel (
B_OPEN_PANEL ,
&MessengerToSelf ,
&DirectoryEntryRef ,
B_FILE_NODE,
true ,
new BMessage (MSG_ESTIMATE_FILE_REFS) ,
NULL ,
false ,
true );
}
if (m_EstimateSpamFilePanelPntr != NULL)
m_EstimateSpamFilePanelPntr->Show ();
}
void
ControlsView::FrameResized (float, float)
{
m_ScoringModeCachedValue = SM_MAX;
m_TokenizeModeCachedValue = TM_MAX;
}
void
ControlsView::MessageReceived (BMessage *MessagePntr)
{
BMessage CommandMessage;
bool TempBool;
uint32 TempUint32;
switch (MessagePntr->what)
{
case MSG_BROWSE_BUTTON:
BrowseForDatabaseFile ();
break;
case MSG_DATABASE_NAME:
if (strcmp (m_DatabaseFileNameCachedValue,
m_DatabaseFileNameTextboxPntr->Text ()) != 0)
SubmitCommandString (PN_DATABASE_FILE, B_SET_PROPERTY,
m_DatabaseFileNameTextboxPntr->Text ());
break;
case MSG_ESTIMATE_BUTTON:
BrowseForFileToEstimate ();
break;
case MSG_ESTIMATE_FILE_REFS:
EstimateRefFilesAndDisplay (MessagePntr);
break;
case MSG_IGNORE_CLASSIFICATION:
TempBool = (m_IgnorePreviousClassCheckboxPntr->Value() == B_CONTROL_ON);
if (m_IgnorePreviousClassCachedValue != TempBool)
SubmitCommandBool (PN_IGNORE_PREVIOUS_CLASSIFICATION,
B_SET_PROPERTY, TempBool);
break;
case MSG_PURGE_AGE:
TempUint32 = strtoul (m_PurgeAgeTextboxPntr->Text (), NULL, 10);
if (m_PurgeAgeCachedValue != TempUint32)
SubmitCommandInt32 (PN_PURGE_AGE, B_SET_PROPERTY, TempUint32);
break;
case MSG_PURGE_POPULARITY:
TempUint32 = strtoul (m_PurgePopularityTextboxPntr->Text (), NULL, 10);
if (m_PurgePopularityCachedValue != TempUint32)
SubmitCommandInt32 (PN_PURGE_POPULARITY, B_SET_PROPERTY, TempUint32);
break;
case MSG_SERVER_MODE:
TempBool = (m_ServerModeCheckboxPntr->Value() == B_CONTROL_ON);
if (m_ServerModeCachedValue != TempBool)
SubmitCommandBool (PN_SERVER_MODE, B_SET_PROPERTY, TempBool);
break;
default:
BView::MessageReceived (MessagePntr);
}
}
void
ControlsView::PollServerForChanges ()
{
ABSApp *MyAppPntr;
BMenuItem *TempMenuItemPntr;
char TempString [PATH_MAX];
BWindow *WindowPntr;
WindowPntr = Window ();
if (WindowPntr == NULL)
return;
if (g_ServerMode != m_ServerModeCachedValue &&
m_ServerModeCheckboxPntr != NULL)
{
m_ServerModeCachedValue = g_ServerMode;
m_ServerModeCheckboxPntr->SetValue (
m_ServerModeCachedValue ? B_CONTROL_ON : B_CONTROL_OFF);
WindowPntr->Minimize (m_ServerModeCachedValue);
}
if (WindowPntr->IsMinimized ())
return;
if (!m_DatabaseLoadDone)
{
m_DatabaseLoadDone = true;
SubmitCommandString (PN_DATABASE_FILE, B_COUNT_PROPERTIES, "");
}
MyAppPntr = dynamic_cast<ABSApp *> (be_app);
if (MyAppPntr == NULL)
return;
if (MyAppPntr->m_PurgeAge != m_PurgeAgeCachedValue &&
m_PurgeAgeTextboxPntr != NULL)
{
m_PurgeAgeCachedValue = MyAppPntr->m_PurgeAge;
sprintf (TempString, "%" B_PRIu32, m_PurgeAgeCachedValue);
m_PurgeAgeTextboxPntr->SetText (TempString);
}
if (MyAppPntr->m_PurgePopularity != m_PurgePopularityCachedValue &&
m_PurgePopularityTextboxPntr != NULL)
{
m_PurgePopularityCachedValue = MyAppPntr->m_PurgePopularity;
sprintf (TempString, "%" B_PRIu32, m_PurgePopularityCachedValue);
m_PurgePopularityTextboxPntr->SetText (TempString);
}
if (MyAppPntr->m_IgnorePreviousClassification !=
m_IgnorePreviousClassCachedValue &&
m_IgnorePreviousClassCheckboxPntr != NULL)
{
m_IgnorePreviousClassCachedValue =
MyAppPntr->m_IgnorePreviousClassification;
m_IgnorePreviousClassCheckboxPntr->SetValue (
m_IgnorePreviousClassCachedValue ? B_CONTROL_ON : B_CONTROL_OFF);
}
if (MyAppPntr->m_TotalGenuineMessages != m_GenuineCountCachedValue &&
m_GenuineCountTextboxPntr != NULL)
{
m_GenuineCountCachedValue = MyAppPntr->m_TotalGenuineMessages;
sprintf (TempString, "%" B_PRIu32, m_GenuineCountCachedValue);
m_GenuineCountTextboxPntr->SetText (TempString);
}
if (MyAppPntr->m_TotalSpamMessages != m_SpamCountCachedValue &&
m_SpamCountTextboxPntr != NULL)
{
m_SpamCountCachedValue = MyAppPntr->m_TotalSpamMessages;
sprintf (TempString, "%" B_PRIu32, m_SpamCountCachedValue);
m_SpamCountTextboxPntr->SetText (TempString);
}
if (MyAppPntr->m_WordCount != m_WordCountCachedValue &&
m_WordCountTextboxPntr != NULL)
{
m_WordCountCachedValue = MyAppPntr->m_WordCount;
sprintf (TempString, "%" B_PRIu32, m_WordCountCachedValue);
m_WordCountTextboxPntr->SetText (TempString);
}
if (MyAppPntr->m_TokenizeMode != m_TokenizeModeCachedValue &&
m_TokenizeModePopUpMenuPntr != NULL)
{
m_TokenizeModeCachedValue = MyAppPntr->m_TokenizeMode;
TempMenuItemPntr =
m_TokenizeModePopUpMenuPntr->ItemAt ((int) m_TokenizeModeCachedValue);
if (TempMenuItemPntr != NULL)
TempMenuItemPntr->SetMarked (true);
}
if (MyAppPntr->m_ScoringMode != m_ScoringModeCachedValue &&
m_ScoringModePopUpMenuPntr != NULL)
{
m_ScoringModeCachedValue = MyAppPntr->m_ScoringMode;
TempMenuItemPntr =
m_ScoringModePopUpMenuPntr->ItemAt ((int) m_ScoringModeCachedValue);
if (TempMenuItemPntr != NULL)
TempMenuItemPntr->SetMarked (true);
}
if (MyAppPntr->LockWithTimeout (100000) != B_OK)
return;
if (strcmp (MyAppPntr->m_DatabaseFileName.String (),
m_DatabaseFileNameCachedValue) != 0 &&
m_DatabaseFileNameTextboxPntr != NULL)
{
strcpy (m_DatabaseFileNameCachedValue,
MyAppPntr->m_DatabaseFileName.String ());
m_DatabaseFileNameTextboxPntr->SetText (m_DatabaseFileNameCachedValue);
WindowPntr->SetTitle (m_DatabaseFileNameCachedValue);
}
MyAppPntr->Unlock ();
}
void
ControlsView::Pulse ()
{
if (system_time () > m_TimeOfLastPoll + 200000)
{
PollServerForChanges ();
m_TimeOfLastPoll = system_time ();
}
}
DatabaseWindow::DatabaseWindow ()
: BWindow (BRect (30, 30, 620, 400),
"Haiku spam filter server",
B_DOCUMENT_WINDOW, B_ASYNCHRONOUS_CONTROLS)
{
BRect TempRect;
m_ControlsViewPntr = new ControlsView (Bounds ());
if (m_ControlsViewPntr == NULL)
goto ErrorExit;
AddChild (m_ControlsViewPntr);
TempRect = Bounds ();
TempRect.top = m_ControlsViewPntr->Frame().bottom + 1;
m_WordsViewPntr = new WordsView (TempRect);
if (m_WordsViewPntr == NULL)
goto ErrorExit;
AddChild (m_WordsViewPntr);
Minimize (g_ServerMode);
return;
ErrorExit:
DisplayErrorMessage ("Unable to initialise the window contents.");
}
void
DatabaseWindow::MessageReceived (BMessage *MessagePntr)
{
if (MessagePntr->what == B_MOUSE_WHEEL_CHANGED)
{
if (m_WordsViewPntr != NULL)
m_WordsViewPntr->MessageReceived (MessagePntr);
}
else
BWindow::MessageReceived (MessagePntr);
}
bool
DatabaseWindow::QuitRequested ()
{
be_app->PostMessage (B_QUIT_REQUESTED);
return true;
}
WordsView::WordsView (BRect NewBounds)
: BView (NewBounds, "WordsView", B_FOLLOW_ALL_SIDES,
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE | B_PULSE_NEEDED),
m_ArrowLineDownPntr (NULL),
m_ArrowLineUpPntr (NULL),
m_ArrowPageDownPntr (NULL),
m_ArrowPageUpPntr (NULL),
m_LastTimeAKeyWasPressed (0)
{
font_height TempFontHeight;
GetFont (&m_TextFont);
m_TextFont.SetSize (ceilf (m_TextFont.Size() * 1.1));
m_TextFont.GetHeight (&TempFontHeight);
SetFont (&m_TextFont);
m_LineHeight = ceilf (TempFontHeight.ascent +
TempFontHeight.descent + TempFontHeight.leading);
m_AscentHeight = ceilf (TempFontHeight.ascent);
m_TextHeight = ceilf (TempFontHeight.ascent +
TempFontHeight.descent);
m_FocusedColour.red = 255;
m_FocusedColour.green = 255;
m_FocusedColour.blue = 255;
m_FocusedColour.alpha = 255;
m_UnfocusedColour.red = 245;
m_UnfocusedColour.green = 245;
m_UnfocusedColour.blue = 255;
m_UnfocusedColour.alpha = 255;
m_BackgroundColour = m_UnfocusedColour;
SetViewColor (m_BackgroundColour);
SetLowColor (m_BackgroundColour);
SetHighColor (0, 0, 0);
strcpy (m_FirstDisplayedWord, "a");
}
void
WordsView::AttachedToWindow ()
{
BPolygon DownLinePolygon (g_DownLinePoints,
sizeof (g_DownLinePoints) /
sizeof (g_DownLinePoints[0]));
BPolygon DownPagePolygon (g_DownPagePoints,
sizeof (g_DownPagePoints) /
sizeof (g_DownPagePoints[0]));
BPolygon UpLinePolygon (g_UpLinePoints,
sizeof (g_UpLinePoints) /
sizeof (g_UpLinePoints[0]));
BPolygon UpPagePolygon (g_UpPagePoints,
sizeof (g_UpPagePoints) /
sizeof (g_UpPagePoints[0]));
BPicture TempOffPicture;
BPicture TempOnPicture;
BRect TempRect;
SetHighColor (0, 0, 0);
BeginPicture (&TempOffPicture);
FillPolygon (&UpLinePolygon);
SetHighColor (180, 180, 180);
StrokePolygon (&UpLinePolygon);
EndPicture ();
SetHighColor (128, 128, 128);
BeginPicture (&TempOnPicture);
FillPolygon (&UpLinePolygon);
EndPicture ();
TempRect = Bounds ();
TempRect.bottom = TempRect.top + 2 * B_H_SCROLL_BAR_HEIGHT;
TempRect.left = TempRect.right - B_V_SCROLL_BAR_WIDTH;
m_ArrowLineUpPntr = new BPictureButton (TempRect, "Up Line",
&TempOffPicture, &TempOnPicture,
new BMessage (MSG_LINE_UP), B_ONE_STATE_BUTTON,
B_FOLLOW_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE);
if (m_ArrowLineUpPntr == NULL) goto ErrorExit;
AddChild (m_ArrowLineUpPntr);
m_ArrowLineUpPntr->SetTarget (this);
SetHighColor (0, 0, 0);
BeginPicture (&TempOffPicture);
FillPolygon (&UpPagePolygon);
SetHighColor (180, 180, 180);
StrokePolygon (&UpPagePolygon);
EndPicture ();
SetHighColor (128, 128, 128);
BeginPicture (&TempOnPicture);
FillPolygon (&UpPagePolygon);
EndPicture ();
TempRect = Bounds ();
TempRect.top += 2 * B_H_SCROLL_BAR_HEIGHT + 1;
TempRect.bottom = TempRect.top + 2 * B_H_SCROLL_BAR_HEIGHT;
TempRect.left = TempRect.right - B_V_SCROLL_BAR_WIDTH;
m_ArrowPageUpPntr = new BPictureButton (TempRect, "Up Page",
&TempOffPicture, &TempOnPicture,
new BMessage (MSG_PAGE_UP), B_ONE_STATE_BUTTON,
B_FOLLOW_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE);
if (m_ArrowPageUpPntr == NULL) goto ErrorExit;
AddChild (m_ArrowPageUpPntr);
m_ArrowPageUpPntr->SetTarget (this);
SetHighColor (0, 0, 0);
BeginPicture (&TempOffPicture);
FillPolygon (&DownPagePolygon);
SetHighColor (180, 180, 180);
StrokePolygon (&DownPagePolygon);
EndPicture ();
SetHighColor (128, 128, 128);
BeginPicture (&TempOnPicture);
FillPolygon (&DownPagePolygon);
EndPicture ();
TempRect = Bounds ();
TempRect.bottom -= 3 * B_H_SCROLL_BAR_HEIGHT + 1;
TempRect.top = TempRect.bottom - 2 * B_H_SCROLL_BAR_HEIGHT;
TempRect.left = TempRect.right - B_V_SCROLL_BAR_WIDTH;
m_ArrowPageDownPntr = new BPictureButton (TempRect, "Down Page",
&TempOffPicture, &TempOnPicture,
new BMessage (MSG_PAGE_DOWN), B_ONE_STATE_BUTTON,
B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM, B_WILL_DRAW | B_NAVIGABLE);
if (m_ArrowPageDownPntr == NULL) goto ErrorExit;
AddChild (m_ArrowPageDownPntr);
m_ArrowPageDownPntr->SetTarget (this);
SetHighColor (0, 0, 0);
BeginPicture (&TempOffPicture);
FillPolygon (&DownLinePolygon);
SetHighColor (180, 180, 180);
StrokePolygon (&DownLinePolygon);
EndPicture ();
SetHighColor (128, 128, 128);
BeginPicture (&TempOnPicture);
FillPolygon (&DownLinePolygon);
EndPicture ();
TempRect = Bounds ();
TempRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
TempRect.top = TempRect.bottom - 2 * B_H_SCROLL_BAR_HEIGHT;
TempRect.left = TempRect.right - B_V_SCROLL_BAR_WIDTH;
m_ArrowLineDownPntr = new BPictureButton (TempRect, "Down Line",
&TempOffPicture, &TempOnPicture,
new BMessage (MSG_LINE_DOWN), B_ONE_STATE_BUTTON,
B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM, B_WILL_DRAW | B_NAVIGABLE);
if (m_ArrowLineDownPntr == NULL) goto ErrorExit;
AddChild (m_ArrowLineDownPntr);
m_ArrowLineDownPntr->SetTarget (this);
return;
ErrorExit:
DisplayErrorMessage ("Problems while making view displaying the words.");
}
void
WordsView::Draw (BRect UpdateRect)
{
float AgeDifference;
float AgeProportion;
float CenterX;
float ColumnLeftCenterX;
float ColumnMiddleCenterX;
float ColumnRightCenterX;
float CompensatedRatio;
StatisticsMap::iterator DataIter;
StatisticsMap::iterator EndIter;
rgb_color FillColour;
float GenuineProportion;
uint32 GenuineSpamSum;
float HeightPixels;
float HeightProportion;
float LeftBounds;
ABSApp *MyAppPntr;
uint32 NewestAge;
uint32 OldestAge;
float OneFifthTotalGenuine;
float OneFifthTotalSpam;
double RawProbabilityRatio;
float RightBounds;
float SpamProportion;
StatisticsPointer StatisticsPntr;
BRect TempRect;
char TempString [PATH_MAX];
float TotalGenuineMessages = 1.0;
float TotalSpamMessages = 1.0;
float Width;
float Y;
MyAppPntr = dynamic_cast<ABSApp *> (be_app);
if (MyAppPntr == NULL || MyAppPntr->LockWithTimeout (100000) != B_OK)
return;
if (MyAppPntr->m_TotalGenuineMessages > 0)
TotalGenuineMessages = MyAppPntr->m_TotalGenuineMessages;
OneFifthTotalGenuine = TotalGenuineMessages / 5;
if (MyAppPntr->m_TotalSpamMessages > 0)
TotalSpamMessages = MyAppPntr->m_TotalSpamMessages;
OneFifthTotalSpam = TotalSpamMessages / 5;
EndIter = MyAppPntr->m_WordMap.end ();
OldestAge = MyAppPntr->m_OldestAge;
NewestAge =
MyAppPntr->m_TotalGenuineMessages + MyAppPntr->m_TotalSpamMessages;
if (NewestAge == 0)
goto NormalExit;
NewestAge--;
AgeDifference = NewestAge - OldestAge;
LeftBounds = Bounds().left;
RightBounds = Bounds().right - B_V_SCROLL_BAR_WIDTH;
Width = RightBounds - LeftBounds;
FillColour.alpha = 255;
CenterX = ceilf (LeftBounds + Width * 0.5);
ColumnLeftCenterX = ceilf (LeftBounds + Width * 0.05);
ColumnMiddleCenterX = CenterX;
ColumnRightCenterX = ceilf (LeftBounds + Width * 0.95);
for (DataIter = MyAppPntr->m_WordMap.lower_bound (m_FirstDisplayedWord),
Y = Bounds().top;
DataIter != EndIter && Y < UpdateRect.bottom;
DataIter++, Y += m_LineHeight)
{
if (Y + m_LineHeight < UpdateRect.top)
continue;
StatisticsPntr = &DataIter->second;
SpamProportion = StatisticsPntr->spamCount / TotalSpamMessages;
GenuineProportion = StatisticsPntr->genuineCount / TotalGenuineMessages;
if (SpamProportion + GenuineProportion > 0.0f)
RawProbabilityRatio =
SpamProportion / (SpamProportion + GenuineProportion);
else
RawProbabilityRatio = g_RobinsonX;
GenuineSpamSum =
StatisticsPntr->spamCount + StatisticsPntr->genuineCount;
CompensatedRatio =
(g_RobinsonS * g_RobinsonX + GenuineSpamSum * RawProbabilityRatio) /
(g_RobinsonS + GenuineSpamSum);
HeightProportion = 0.5f * (StatisticsPntr->genuineCount /
OneFifthTotalGenuine + StatisticsPntr->spamCount / OneFifthTotalSpam);
if (HeightProportion > 1.0f)
HeightProportion = 1.0f;
HeightPixels = ceilf (HeightProportion * m_TextHeight);
if (AgeDifference <= 0.0f)
AgeProportion = 1.0;
else
AgeProportion = (StatisticsPntr->age - OldestAge) / AgeDifference;
TempRect.top = ceilf (Y + m_TextHeight / 2 - HeightPixels / 2);
TempRect.bottom = TempRect.top + HeightPixels;
if (CompensatedRatio < 0.5f)
{
TempRect.left = ceilf (
CenterX - 1.6f * (0.5f - CompensatedRatio) * (CenterX - LeftBounds));
TempRect.right = CenterX;
FillColour.red = 230 - (int) (AgeProportion * 230.0f);
FillColour.green = FillColour.red;
FillColour.blue = 255;
}
else
{
TempRect.left = CenterX;
TempRect.right = ceilf (
CenterX + 1.6f * (CompensatedRatio - 0.5f) * (RightBounds - CenterX));
FillColour.blue = 230 - (int) (AgeProportion * 230.0f);
FillColour.green = FillColour.blue;
FillColour.red = 255;
}
SetHighColor (FillColour);
SetDrawingMode (B_OP_COPY);
FillRect (TempRect);
SetHighColor (0, 0, 0);
SetDrawingMode (B_OP_OVER);
sprintf (TempString, "%" B_PRIu32, StatisticsPntr->genuineCount);
Width = m_TextFont.StringWidth (TempString);
MovePenTo (ceilf (ColumnLeftCenterX - Width / 2), Y + m_AscentHeight);
DrawString (TempString);
strcpy (TempString, DataIter->first.c_str ());
Width = m_TextFont.StringWidth (TempString);
MovePenTo (ceilf (ColumnMiddleCenterX - Width / 2), Y + m_AscentHeight);
DrawString (TempString);
sprintf (TempString, "%" B_PRIu32, StatisticsPntr->spamCount);
Width = m_TextFont.StringWidth (TempString);
MovePenTo (ceilf (ColumnRightCenterX - Width / 2), Y + m_AscentHeight);
DrawString (TempString);
}
Width = m_TextFont.StringWidth (m_FirstDisplayedWord);
if (Width > 0)
{
TempRect = Bounds ();
TempRect.top += 4 * B_H_SCROLL_BAR_HEIGHT + 1;
TempRect.bottom -= 5 * B_H_SCROLL_BAR_HEIGHT + 1;
MovePenTo (TempRect.right - m_TextHeight + m_AscentHeight - 1,
ceilf ((TempRect.bottom + TempRect.top) / 2 + Width / 2));
m_TextFont.SetRotation (90);
SetFont (&m_TextFont, B_FONT_ROTATION);
DrawString (m_FirstDisplayedWord);
m_TextFont.SetRotation (0);
SetFont (&m_TextFont, B_FONT_ROTATION);
}
NormalExit:
m_CachedTotalGenuineMessages = MyAppPntr->m_TotalGenuineMessages;
m_CachedTotalSpamMessages = MyAppPntr->m_TotalSpamMessages;
m_CachedWordCount = MyAppPntr->m_WordCount;
MyAppPntr->Unlock ();
}
void
WordsView::KeyDown (const char *BufferPntr, int32 NumBytes)
{
int32 CharLength;
bigtime_t CurrentTime;
char TempString [40];
CurrentTime = system_time ();
if (NumBytes < (int32) sizeof (TempString))
{
memcpy (TempString, BufferPntr, NumBytes);
TempString [NumBytes] = 0;
CharLength = strlen (TempString);
if (CharLength == 1 &&
(TempString[0] == B_UP_ARROW ||
TempString[0] == B_DOWN_ARROW ||
TempString[0] == B_PAGE_UP ||
TempString[0] == B_PAGE_DOWN))
{
MoveTextUpOrDown ((TempString[0] == B_UP_ARROW) ? MSG_LINE_UP :
((TempString[0] == B_DOWN_ARROW) ? MSG_LINE_DOWN :
((TempString[0] == B_PAGE_UP) ? MSG_PAGE_UP : MSG_PAGE_DOWN)));
}
else if (CharLength > 1 ||
(CharLength == 1 && 32 <= (uint8) TempString[0]))
{
if (CurrentTime - m_LastTimeAKeyWasPressed >= 1000000 )
strcpy (m_FirstDisplayedWord, TempString);
else if (strlen (m_FirstDisplayedWord) + CharLength <= g_MaxWordLength)
strcat (m_FirstDisplayedWord, TempString);
Invalidate ();
}
}
m_LastTimeAKeyWasPressed = CurrentTime;
BView::KeyDown (BufferPntr, NumBytes);
}
void
WordsView::MakeFocus (bool Focused)
{
if (Focused)
m_BackgroundColour = m_FocusedColour;
else
m_BackgroundColour = m_UnfocusedColour;
SetViewColor (m_BackgroundColour);
SetLowColor (m_BackgroundColour);
if (m_ArrowLineDownPntr != NULL)
{
m_ArrowLineDownPntr->SetViewColor (m_BackgroundColour);
m_ArrowLineDownPntr->Invalidate ();
}
if (m_ArrowLineUpPntr != NULL)
{
m_ArrowLineUpPntr->SetViewColor (m_BackgroundColour);
m_ArrowLineUpPntr->Invalidate ();
}
if (m_ArrowPageDownPntr != NULL)
{
m_ArrowPageDownPntr->SetViewColor (m_BackgroundColour);
m_ArrowPageDownPntr->Invalidate ();
}
if (m_ArrowPageUpPntr != NULL)
{
m_ArrowPageUpPntr->SetViewColor (m_BackgroundColour);
m_ArrowPageUpPntr->Invalidate ();
}
Invalidate ();
BView::MakeFocus (Focused);
}
void
WordsView::MessageReceived (BMessage *MessagePntr)
{
int32 CountFound;
float DeltaY;
type_code TypeFound;
switch (MessagePntr->what)
{
case B_MOUSE_WHEEL_CHANGED:
if (MessagePntr->FindFloat ("be:wheel_delta_y", &DeltaY) != 0) break;
if (DeltaY < 0)
MoveTextUpOrDown (MSG_LINE_UP);
else if (DeltaY > 0)
MoveTextUpOrDown (MSG_LINE_DOWN);
break;
case MSG_LINE_DOWN:
case MSG_LINE_UP:
case MSG_PAGE_DOWN:
case MSG_PAGE_UP:
MoveTextUpOrDown (MessagePntr->what);
break;
case B_SIMPLE_DATA:
if (MessagePntr->GetInfo ("refs", &TypeFound, &CountFound) == B_OK &&
CountFound > 0 && TypeFound == B_REF_TYPE)
{
RefsDroppedHere (MessagePntr);
break;
}
default:
BView::MessageReceived (MessagePntr);
}
}
void
WordsView::MouseDown (BPoint)
{
if (!IsFocus ())
MakeFocus (true);
}
void
WordsView::MoveTextUpOrDown (uint32 MovementType)
{
StatisticsMap::iterator DataIter;
int i;
ABSApp *MyAppPntr;
int PageSize;
MyAppPntr = dynamic_cast<ABSApp *> (be_app);
if (MyAppPntr == NULL || MyAppPntr->LockWithTimeout (2000000) != B_OK)
return;
PageSize = (int) (Bounds().Height() / m_LineHeight - 1);
if (PageSize < 1)
PageSize = 1;
DataIter = MyAppPntr->m_WordMap.lower_bound (m_FirstDisplayedWord);
switch (MovementType)
{
case MSG_LINE_UP:
if (DataIter != MyAppPntr->m_WordMap.begin ())
DataIter--;
break;
case MSG_LINE_DOWN:
if (DataIter != MyAppPntr->m_WordMap.end ())
DataIter++;
break;
case MSG_PAGE_UP:
for (i = 0; i < PageSize; i++)
{
if (DataIter == MyAppPntr->m_WordMap.begin ())
break;
DataIter--;
}
break;
case MSG_PAGE_DOWN:
for (i = 0; i < PageSize; i++)
{
if (DataIter == MyAppPntr->m_WordMap.end ())
break;
DataIter++;
}
break;
}
if (DataIter != MyAppPntr->m_WordMap.end ())
strcpy (m_FirstDisplayedWord, DataIter->first.c_str ());
Invalidate ();
MyAppPntr->Unlock ();
}
void
WordsView::Pulse ()
{
ABSApp *MyAppPntr;
MyAppPntr = dynamic_cast<ABSApp *> (be_app);
if (MyAppPntr == NULL)
return;
if (MyAppPntr->m_TotalGenuineMessages != m_CachedTotalGenuineMessages ||
MyAppPntr->m_TotalSpamMessages != m_CachedTotalSpamMessages ||
MyAppPntr->m_WordCount != m_CachedWordCount)
Invalidate ();
}
void
WordsView::RefsDroppedHere (BMessage *MessagePntr)
{
float Left;
bool SpamExample = true;
float Third;
BPoint WhereDropped;
if (MessagePntr->FindPoint ("_drop_point_", &WhereDropped) != B_OK)
return;
ConvertFromScreen (&WhereDropped);
Third = Bounds().Width() / 3;
Left = Bounds().left;
if (WhereDropped.x < Left + Third)
SpamExample = false;
else if (WhereDropped.x < Left + 2 * Third)
{
EstimateRefFilesAndDisplay (MessagePntr);
return;
}
if (g_CommanderLooperPntr != NULL)
g_CommanderLooperPntr->CommandReferences (
MessagePntr, true , SpamExample ? CL_SPAM : CL_GENUINE);
}
int main (int argc, char**)
{
g_CommandLineMode = (argc > 1);
if (!g_CommandLineMode)
cout << PrintUsage;
g_CommanderLooperPntr = new CommanderLooper;
if (g_CommanderLooperPntr != NULL)
{
g_CommanderMessenger = new BMessenger (NULL, g_CommanderLooperPntr);
g_CommanderLooperPntr->Run ();
}
ABSApp MyApp;
if (MyApp.InitCheck () == 0)
{
MyApp.LoadSaveSettings (true );
MyApp.Run ();
}
if (g_CommanderLooperPntr != NULL)
{
g_CommanderLooperPntr->PostMessage (B_QUIT_REQUESTED);
snooze (100000);
}
cerr << "SpamDBM shutting down..." << endl;
return 0;
}