eudora-mac/statmng.c

1 line
73 KiB
C
Executable File
Raw Permalink Blame History

/* Copyright (c) 2017, Computer History Museum
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
the limitations in the disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE. */
#include "statmng.h"
#define FILE_NUM 134
/* Copyright (c) 2000 by Qualcomm, Inc */
enum
{
kStatCount = kBeginShortStats,
kShortStatCount = kEndStats-kBeginShortStats,
kSaveStateSecs = 15*60, // Save playlist state every 15 minutes
kStatFileVersion = 2,
kDayStatCount = 24,
kWeekStatCount = 7,
kMonthStatCount = 31,
kYearStatCount = 12,
kAutoUpdateTicks = 120 // Auto udpate display 2 seconds after last change in stats
};
enum { kFacetimeOther,kFacetimeRead,kFacetimeWrite };
enum { kThisStat,kLastStat,kAveStat,kTotalStat };
/************************************************************************
* data
************************************************************************/
typedef struct
{
long day[kDayStatCount];
long week[kWeekStatCount];
long month[kMonthStatCount];
long year[kYearStatCount];
} PeriodicStats;
typedef struct
{
PeriodicStats current;
PeriodicStats last;
PeriodicStats average;
long total;
} NumericStats;
typedef struct
{
long day;
long week;
long month;
long year;
} ShortPeriodicStats;
typedef struct
{
ShortPeriodicStats current;
ShortPeriodicStats last;
ShortPeriodicStats average;
long total;
} ShortNumericStats;
typedef struct
{
short version;
long startTime; // When did we start collecting stats?
long junkStartTime; // When did we start collecting junk stats?
long currentTime;
long unused1 ; // totalDays;
long unused2 ; // totalWeeks;
long unused3 ; // totalMonths;
long unused4 ; // totalYears;
NumericStats numStats[kStatCount];
ShortNumericStats shortStats[kShortStatCount];
} StatData, *StatDataPtr, **StatDataHandle;
typedef struct
{
short rThis,rLast;
Str32 sProjected;
StatTimePeriod period;
short rPeriod;
Boolean showAverage;
} ComposeStatsData;
/************************************************************************
* globals
************************************************************************/
static StatDataHandle gStatData;
static LongDateRec gCurrentTime;
static Boolean gStatsDirty;
static FMBHandle gFaceMeasure;
static short gFacetimeMode;
static uLong gAutoUpdateTicks;
static short dataCountTab[] = { kDayStatCount,kWeekStatCount,kMonthStatCount,kYearStatCount };
static short kwdStrTab[] = { ksStatReceivedMail,ksStatSentMail,ksStatFaceTime,
ksStatScoredJunk, ksStatScoredNotJunk, ksStatWhiteList,
ksStatFalsePositives, ksStatFalseNegatives, ksStatFalseWhiteList,0,0,0,
0,
ksStatReceivedAttach,ksStatSentAttach,ksStatReadMsg,0,ksStatForwardMsg,
ksStatReplyMsg,ksStatRedirectMsg,ksStatFaceTimeRead,ksStatFaceTimeCompose,ksStatFaceTimeOther };
#define CalcedStat(i) (i>=kStatJunkTotal && i<=kStatJunkPercent)
/************************************************************************
* prototypes
************************************************************************/
static OSErr ReadStatData(void);
static OSErr WriteStatData(void);
static void CheckTimeChange(void);
static OSErr LoadStats(void);
static OSErr SaveStats(Boolean force);
static void UpdateFacetime(void);
static void UpdateFacetimeLo(void);
static void GetStatTotals(NumericStats *stats,StatTimePeriod period,uLong values[],uLong startTime);
void GetStatTotalPercents(NumericStats *statNumerator,NumericStats *statDenominator,StatTimePeriod period,uLong values[]);
static long SumArray(long *values,short len);
#define SumArrayHi(a) SumArray(a,sizeof(a)/sizeof(a[0]))
static PicHandle GetGraphPict(NumericStats *stats,StatTimePeriod period,Boolean seconds,Boolean showAverage,StringPtr sThis,StringPtr sLast,StringPtr sAverage);
static Handle GetXLabels(StatTimePeriod period);
static UPtr RightAlign7(uLong value,UPtr s);
static UPtr RightAlign7Dec(uLong value,UPtr s);
static UPtr RightAlign(uLong value,UPtr s,short minLen,short decPlace);
static void AddToAve(uLong *to,uLong *from,short length);
static void UpdateNumStatLo(StatType which, long value);
static OSErr GetAbbrevNames(Handle *monthNames, Handle *dayNames);
static void MoveNames(Str15 *names, long count, long abbrLen, StringPtr dest);
static void ComposeNumStats(AccuPtr a,StatType type,short strType,ComposeStatsData *data);
static void ComposeShortStats(AccuPtr a,StatType type,short strType,ComposeStatsData *data,short rObject,Boolean percent);
static void ComposeUsageActivities(AccuPtr a, ComposeStatsData *data);
static PicHandle InitGraphPict(GraphData *data,SeriesInfo *theSeries,StatTimePeriod period,StringPtr *labels);
static void FinishGraphPict(GraphData *data);
static void ComposeFRR(AccuPtr a, ComposeStatsData *data);
void ComposeJunkJunk(AccuPtr a, ComposeStatsData *data);
static uLong CalcElapsedUnits(StatTimePeriod period);
static void AccuAddNumStats(AccuPtr a,short keyword,PeriodicStats *stats);
static void AccuAddArray(AccuPtr a,short keyword,uLong *stats,short count);
static void AccuAddShortStats(AccuPtr a,short keyword,ShortPeriodicStats *stats);
static void ParseStatFile(Handle hStatXML);
static short WordStrn(short rStrn,long value);
void UpdateCalcStats(void);
typedef enum { opPlus, opMinus, opPercent } OpType;
void NumericStatOperation(StatType destination,StatType increment,OpType op);
void PeriodicStatOperation(PeriodicStats *destP, PeriodicStats *incP, OpType op);
void LongArrayOperation(long *array,long *sub,short n,OpType op);
/************************************************************************
* InitStats - initialize statistics
************************************************************************/
void InitStats(void)
{
gFaceMeasure = NewFaceMeasure();
FaceMeasureBegin(gFaceMeasure);
gFacetimeMode = kFacetimeOther;
LoadStats();
}
/************************************************************************
* ShutdownStats - shutdown statistics
************************************************************************/
void ShutdownStats(void)
{
if (!gStatData) return;
UpdateFacetime();
SaveStats(true);
ZapHandle(gStatData);
DisposeFaceMeasure(gFaceMeasure);
gFaceMeasure = nil;
}
/************************************************************************
* StatsIdle - idle time processing for statistics
************************************************************************/
void StatsIdle(void)
{
static uLong nextIdle,nextSaveStatus;
short facetimeMode;
WindowPtr winWP;
uLong seconds;
if (!gStatData || InBG) return;
if (nextIdle && TickCount()<nextIdle) return; // Once a second is sufficient
nextIdle = TickCount() + 60;
facetimeMode = kFacetimeOther;
if ((winWP=MyFrontWindow()) && IsMyWindow(winWP))
{
TOCHandle tocH;
switch (GetWindowKind(winWP))
{
case COMP_WIN:
facetimeMode = kFacetimeWrite;
break;
case MESS_WIN:
facetimeMode = kFacetimeRead;
break;
case MBOX_WIN:
case CBOX_WIN:
tocH = (TOCHandle) GetWindowPrivateData (winWP);
if ((*tocH)->previewPTE)
facetimeMode = kFacetimeRead;
break;
}
}
if (facetimeMode != gFacetimeMode)
{
UpdateFacetime();
gFacetimeMode = facetimeMode;
}
// Save stats on disk every 15 minutes in case of system failure
seconds = LocalDateTime();
if (nextSaveStatus)
{
if (seconds >= nextSaveStatus)
{
SaveStats(false);
nextSaveStatus = seconds + kSaveStateSecs;
}
}
else
nextSaveStatus = seconds + kSaveStateSecs;
if (gAutoUpdateTicks && (TickCount() >= gAutoUpdateTicks))
{
RedisplayStats();
gAutoUpdateTicks = 0;
}
}
/************************************************************************
* UpdateNumStat - update a statistic
************************************************************************/
void UpdateNumStat(StatType which, long value)
{
CheckTimeChange();
UpdateNumStatLo(which,value);
}
/************************************************************************
* UpdateNumStatWithTime - update a statistic specifying the time
************************************************************************/
void UpdateNumStatWithTime(StatType which, long value, uLong seconds)
{
uLong curSeconds;
NumericStats *pNum = nil;
ShortNumericStats *pShort = nil;
if (!gStatData) return;
CheckTimeChange();
curSeconds = LocalDateTime();
if (seconds > curSeconds)
// If time is in future, just use current time
UpdateNumStatLo(which,value);
else if (value)
{
StatDataPtr pData;
LongDateCvt theTime;
LongDateRec dt;
short dYear,dMonth,dWeek,dDay;
theTime.hl.lHigh = 0;
theTime.hl.lLow = seconds;
LongSecondsToDate(&theTime.c,&dt);
dDay = gCurrentTime.ld.dayOfYear - dt.ld.dayOfYear;
dWeek = gCurrentTime.ld.weekOfYear - dt.ld.weekOfYear;
dMonth = gCurrentTime.ld.month - dt.ld.month;
dYear = gCurrentTime.ld.year - dt.ld.year;
pData = *gStatData;
if (which < kStatCount)
{
pNum = &pData->numStats[which];
if (dYear==0)
{
// current year
pNum->current.year[dt.ld.month-1] += value;
if (dMonth==0)
pNum->current.month[dt.ld.day-1] += value; // current month
else
{
if (dMonth==1)
pNum->last.month[dt.ld.day-1] += value; // last month
pNum->average.month[dt.ld.day-1] += value; // add to average
}
if (dWeek==0)
pNum->current.week[dt.ld.dayOfWeek-1] += value; // current week
else
{
if (dWeek==1)
pNum->last.week[dt.ld.dayOfWeek-1] += value; // last week
pNum->average.week[dt.ld.dayOfWeek-1] += value; // add to average
}
if (dDay==0)
pNum->current.day[dt.ld.hour] += value; // today
else
{
if (dDay==1)
pNum->last.day[dt.ld.hour] += value; // yesterday
pNum->average.day[dt.ld.hour] += value; // add to average
}
}
else
{
if (dYear==1)
{
// last year
pNum->last.year[dt.ld.month-1] += value;
if (dt.ld.month==12 && gCurrentTime.ld.month==1)
pNum->last.month[dt.ld.day-1] += value; // last month
if (dt.ld.weekOfYear==52 && gCurrentTime.ld.weekOfYear==1)
pNum->last.week[dt.ld.dayOfWeek-1] += value; // last week
}
pNum->average.year[dt.ld.month-1] += value; // add to average
}
pNum->total += value;
}
else if (which < kEndStats)
{
pShort = &pData->shortStats[which-kBeginShortStats-1];
if (dYear==0)
{
// current year
pShort->current.year += value;
if (dMonth==0)
pShort->current.month += value; // current month
else
{
if (dMonth==1)
pShort->last.month += value; // last month
pShort->average.month += value;
}
if (dWeek==0)
pShort->current.week += value; // current week
else
{
if (dWeek==1)
pShort->last.week += value; // last week
pShort->average.week += value;
}
if (dDay==0)
pShort->current.day += value; // today
else
{
if (dDay==1)
pShort->last.day += value; // yesterday
pShort->average.day += value;
}
}
else
{
if (dYear==1)
{
// last year
pShort->last.year += value;
if (dt.ld.month==12 && gCurrentTime.ld.month==1)
pShort->last.month += value; // last month
if (dt.ld.weekOfYear==52 && gCurrentTime.ld.weekOfYear==1)
pShort->last.week += value; // last week
}
pShort->average.year += value;
}
pShort->total += value;
}
gStatsDirty = true;
gAutoUpdateTicks = TickCount() + kAutoUpdateTicks;
}
}
/************************************************************************
* UpdateNumStatLo - update a statistic
************************************************************************/
static void UpdateNumStatLo(StatType which, long value)
{
StatDataPtr pData;
NumericStats *pNum = nil;
ShortNumericStats *pShort = nil;
if (LoadStats())
return;
if (value)
{
pData = *gStatData;
if (which < kStatCount)
{
pNum = &pData->numStats[which];
pNum->current.day[gCurrentTime.ld.hour] += value;
pNum->current.week[gCurrentTime.ld.dayOfWeek-1] += value;
pNum->current.month[gCurrentTime.ld.day-1] += value;
pNum->current.year[gCurrentTime.ld.month-1] += value;
pNum->total += value;
}
else if (which < kEndStats)
{
pShort = &pData->shortStats[which-kBeginShortStats-1];
pShort->current.day += value;
pShort->current.week += value;
pShort->current.month += value;
pShort->current.year += value;
pShort->total += value;
}
gStatsDirty = true;
gAutoUpdateTicks = TickCount() + kAutoUpdateTicks;
}
}
/************************************************************************
* CheckTimeChange - see if we have gone to a new time period
************************************************************************/
void CheckTimeChange(void)
{
LongDateRec dt,dtStart;
LongDateCvt theTime;
enum
{
kNewYear=0x01,kSkipYear=0x02,kNewMonth=0x04,kSkipMonth=0x08,
kNewWeek=0x10,kSkipWeek=0x20,kNewDay=0x40,kSkipDay=0x80,
kStartYear=0x100,kStartMonth=0x200,kStartWeek=0x400,kStartDay=0x800
} timeFlags;
if (!gStatData) return;
theTime.hl.lHigh = 0;
GetDateTime(&theTime.hl.lLow);
LongSecondsToDate(&theTime.c,&dt);
theTime.hl.lLow = (*gStatData)->startTime;
LongSecondsToDate(&theTime.c,&dtStart);
timeFlags = 0;
if (dt.ld.hour != gCurrentTime.ld.hour)
// facetime needs to be updated at least every hour
UpdateFacetimeLo();
if (dt.ld.year > gCurrentTime.ld.year)
{
// Happy New Year!
timeFlags |= kNewYear+kNewMonth+kNewDay;
if (dt.ld.year - gCurrentTime.ld.year > 1)
// Skipped a year
timeFlags |= kSkipYear+kSkipMonth+kSkipWeek+kSkipDay;
if (dtStart.ld.year==gCurrentTime.ld.year)
// don't include very first month in average
timeFlags |= kStartYear;
}
if (dt.ld.month > gCurrentTime.ld.month || dt.ld.year > gCurrentTime.ld.year)
{
// New month
timeFlags |= kNewMonth+kNewDay;
if (dt.ld.month - gCurrentTime.ld.month > 1)
// Skipped a month
timeFlags |= kSkipMonth+kSkipWeek+kSkipDay;
if (dtStart.ld.year==gCurrentTime.ld.year && dtStart.ld.month==gCurrentTime.ld.month)
// don't include very first day in average
timeFlags |= kStartMonth;
}
if (dt.ld.weekOfYear > gCurrentTime.ld.weekOfYear || dt.ld.year > gCurrentTime.ld.year)
{
// New week
timeFlags |= kNewWeek+kNewDay;
if (dt.ld.weekOfYear - gCurrentTime.ld.weekOfYear > 1)
// Skipped a week
timeFlags |= kSkipWeek+kSkipDay;
if (dtStart.ld.year==gCurrentTime.ld.year && dtStart.ld.weekOfYear==gCurrentTime.ld.weekOfYear)
// don't include very first day in average
timeFlags |= kStartWeek;
}
if (dt.ld.dayOfYear > gCurrentTime.ld.dayOfYear || dt.ld.year > gCurrentTime.ld.year)
{
// New day
timeFlags |= kNewDay;
if (dt.ld.dayOfYear - gCurrentTime.ld.dayOfYear > 1)
// Skipped a day
timeFlags |= kSkipDay;
if (dtStart.ld.year==gCurrentTime.ld.year && dtStart.ld.dayOfYear==gCurrentTime.ld.dayOfYear)
// don't include very first hour in average
timeFlags |= kStartDay;
}
if (timeFlags)
{
short i;
NumericStats *pStats;
ShortNumericStats *pShortStats;
LDRef(gStatData);
// Do numeric stats
for(i=0,pStats=&(*gStatData)->numStats;i<kStatCount;i++,pStats++)
{
// Do year
if (timeFlags&kNewYear)
{
BMD(pStats->current.year,pStats->last.year,sizeof(pStats->current.year));
if (timeFlags&kStartYear)
// don't use first partial month
pStats->current.year[dtStart.ld.month-1] = 0;
AddToAve(pStats->average.year,pStats->current.year,kYearStatCount);
Zero(pStats->current.year);
if (timeFlags&kSkipYear)
Zero(pStats->last.year);
}
// Do month
if (timeFlags&kNewMonth)
{
BMD(pStats->current.month,pStats->last.month,sizeof(pStats->current.month));
if (timeFlags&kStartMonth)
// don't use first partial day
pStats->current.month[dtStart.ld.day-1] = 0;
AddToAve(pStats->average.month,pStats->current.month,kMonthStatCount);
Zero(pStats->current.month);
if (timeFlags&kSkipMonth)
Zero(pStats->last.month);
}
// Do week
if (timeFlags&kNewWeek)
{
BMD(pStats->current.week,pStats->last.week,sizeof(pStats->current.week));
if (timeFlags&kStartWeek)
// don't use first partial day
pStats->current.week[dtStart.ld.dayOfWeek-1] = 0;
AddToAve(pStats->average.week,pStats->current.week,kWeekStatCount);
Zero(pStats->current.week);
if (timeFlags&kSkipWeek)
Zero(pStats->last.week);
}
// Do day
if (timeFlags&kNewDay)
{
BMD(pStats->current.day,pStats->last.day,sizeof(pStats->current.day));
if (timeFlags&kStartDay)
// don't use first partial hour
pStats->current.day[dtStart.ld.hour] = 0;
AddToAve(pStats->average.day,pStats->current.day,kDayStatCount);
Zero(pStats->current.day);
if (timeFlags&kSkipDay)
Zero(pStats->last.day);
}
}
// Do short stats
for(i=0,pShortStats=&(*gStatData)->shortStats;i<kShortStatCount;i++,pShortStats++)
{
// Do year
if (timeFlags&kNewYear)
{
if (timeFlags&kSkipYear)
pShortStats->current.year = 0;
pShortStats->last.year = pShortStats->current.year;
pShortStats->current.year = 0;
}
// Do month
if (timeFlags&kNewMonth)
{
if (timeFlags&kSkipMonth)
pShortStats->current.month = 0;
pShortStats->last.month = pShortStats->current.month;
pShortStats->current.month = 0;
}
// Do week
if (timeFlags&kNewWeek)
{
if (timeFlags&kSkipWeek)
pShortStats->current.week = 0;
pShortStats->last.week = pShortStats->current.week;
pShortStats->current.week = 0;
}
// Do day
if (timeFlags&kNewDay)
{
if (timeFlags&kSkipDay)
pShortStats->current.day = 0;
pShortStats->last.day = pShortStats->current.day;
pShortStats->current.day = 0;
}
}
gAutoUpdateTicks = TickCount() + kAutoUpdateTicks;
UL(gStatData);
}
gCurrentTime = dt;
}
/************************************************************************
* AddToAve - add current stats to average
************************************************************************/
static void AddToAve(uLong *to,uLong *from,short length)
{
while(length--)
*to++ += *from++;
}
/************************************************************************
* LoadStats - load stats from file in XML format
************************************************************************/
OSErr LoadStats(void)
{
OSErr err;
FSSpec spec,renameSpec;
Str255 s;
Handle hStatXML;
LongDateCvt theTime;
if (gStatData) return noErr; // Already got it
gStatData = NewZH(StatData);
if (!gStatData) return MemError ();
(*gStatData)->startTime = (*gStatData)->junkStartTime = (*gStatData)->currentTime = LocalDateTime(); // in case we can't get any saved stats
FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(s,STATISTICS_FILE),&spec);
err = Snarf(&spec,&hStatXML,nil);
if (!err)
{
ParseStatFile(hStatXML);
ZapHandle(hStatXML);
}
else if (!FSpExists(&spec))
{
// There was a problem loading the stats data. Either the file doesn't exist yet
// or there was a file error. In the latter case, rename the bad file so we have
// a backup and so we can create a new one.
renameSpec = spec;
PSCat(renameSpec.name,"\p.bad");
UniqueSpec(&renameSpec,31);
FSpRename(&spec,renameSpec.name);
}
theTime.hl.lHigh = 0;
theTime.hl.lLow = (*gStatData)->currentTime;
LongSecondsToDate(&theTime.c,&gCurrentTime);
CheckTimeChange();
return err;
}
/************************************************************************
* SaveStats - save stats to file in XML format
************************************************************************/
OSErr SaveStats(Boolean force)
{
OSErr err;
FSSpec spec;
Str255 s,sValue;
Accumulator a;
StatDataPtr pStatData;
short i;
if (!gStatData || !gStatsDirty) return noErr;
if (!force && !DiskSpunUp()) return noErr; // if the disk isn't spun up, bail
CheckTimeChange();
(*gStatData)->currentTime = LocalDateTime();
pStatData = LDRef(gStatData);
// start XML
AccuInit(&a);
AccuAddStr(&a,GetRString(s,StatXMLStrn+ksStatXMLVersion));
AccuAddCRLF(&a);
AccuAddTagLine(&a,GetRString(s,StatXMLStrn+ksStatDocEntry),false);
XMLIncIndent();
// initial data
AccuAddXMLObjectInt(&a,GetRString(s,StatXMLStrn+ksStatVersion),kStatFileVersion); // version
AccuAddXMLObject(&a,GetRString(s,StatXMLStrn+ksStatStartTime),R822Date(sValue,pStatData->startTime-ZoneSecs())); // start time
AccuAddXMLObject(&a,GetRString(s,StatXMLStrn+ksStatJunkStartTime),R822Date(sValue,pStatData->junkStartTime-ZoneSecs())); // junk start time
AccuAddXMLObject(&a,GetRString(s,StatXMLStrn+ksStatCurrentTime),R822Date(sValue,pStatData->currentTime-ZoneSecs())); // current time
AccuAddCRLF(&a);
// number stats
for(i=0;i<kEndStats;i++)
{
if (i==kBeginShortStats || i==kStatUnused || CalcedStat(i)) continue;
AccuAddTagLine(&a,GetRString(s,StatXMLStrn+kwdStrTab[i]),false);
XMLIncIndent();
if (i < kBeginShortStats)
{
// number stats
AccuAddNumStats(&a,StatXMLStrn+ksStatCurrent,&pStatData->numStats[i].current);
AccuAddNumStats(&a,StatXMLStrn+ksStatPrevious,&pStatData->numStats[i].last);
AccuAddNumStats(&a,StatXMLStrn+ksStatAveSum,&pStatData->numStats[i].average);
AccuAddXMLObjectInt(&a,GetRString(s,StatXMLStrn+ksStatTotal),pStatData->numStats[i].total);
}
else
{
// short number stats
AccuAddShortStats(&a,StatXMLStrn+ksStatCurrent,&pStatData->shortStats[i-kBeginShortStats-1].current);
AccuAddShortStats(&a,StatXMLStrn+ksStatPrevious,&pStatData->shortStats[i-kBeginShortStats-1].last);
AccuAddShortStats(&a,StatXMLStrn+ksStatAveSum,&pStatData->shortStats[i-kBeginShortStats-1].average);
AccuAddXMLObjectInt(&a,GetRString(s,StatXMLStrn+ksStatTotal),pStatData->shortStats[i-kBeginShortStats-1].total);
}
XMLDecIndent();
AccuAddTagLine(&a,GetRString(s,StatXMLStrn+kwdStrTab[i]),true);
AccuAddCRLF(&a);
}
// end XML
XMLDecIndent();
AccuAddTagLine(&a,GetRString(s,StatXMLStrn+ksStatDocEntry),true);
AccuTrim(&a);
UL(gStatData);
// save out to file
FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(s,STATISTICS_FILE),&spec);
err = Blat(&spec,a.data,false);
AccuZap(a);
gStatsDirty = false;
return err;
}
/************************************************************************
* ResetStatistics - throw away the stats and start all over
************************************************************************/
void ResetStatistics(void)
{
FSSpec spec;
Str255 s;
ShutdownStats();
FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(s,STATISTICS_FILE),&spec);
FSpTrash(&spec);
InitStats();
}
/************************************************************************
* AccuAddShortStats - save short stats
************************************************************************/
static void AccuAddShortStats(AccuPtr a,short keyword,ShortPeriodicStats *stats)
{
Str63 s;
AccuAddTagLine(a,GetRString(s,keyword),false);
XMLIncIndent();
AccuAddXMLObjectInt(a,GetRString(s,StatXMLStrn+ksStatDay),stats->day);
AccuAddXMLObjectInt(a,GetRString(s,StatXMLStrn+ksStatWeek),stats->week);
AccuAddXMLObjectInt(a,GetRString(s,StatXMLStrn+ksStatMonth),stats->month);
AccuAddXMLObjectInt(a,GetRString(s,StatXMLStrn+ksStatYear),stats->year);
XMLDecIndent();
AccuAddTagLine(a,GetRString(s,keyword),true);
}
/************************************************************************
* AccuAddNumStats - save number stats
************************************************************************/
static void AccuAddNumStats(AccuPtr a,short keyword,PeriodicStats *stats)
{
Str63 s;
AccuAddTagLine(a,GetRString(s,keyword),false);
XMLIncIndent();
AccuAddArray(a,StatXMLStrn+ksStatDay,&stats->day,kDayStatCount);
AccuAddArray(a,StatXMLStrn+ksStatWeek,&stats->week,kWeekStatCount);
AccuAddArray(a,StatXMLStrn+ksStatMonth,&stats->month,kMonthStatCount);
AccuAddArray(a,StatXMLStrn+ksStatYear,&stats->year,kYearStatCount);
XMLDecIndent();
AccuAddTagLine(a,GetRString(s,keyword),true);
}
/************************************************************************
* AccuAddArray - save an array of data
************************************************************************/
static void AccuAddArray(AccuPtr a,short keyword,uLong *stats,short count)
{
Str63 s;
short i;
AccuIndent(a);
AccuAddTag(a,GetRString(s,keyword),false);
for(i=0;i<count;i++)
{
if (i) AccuAddChar(a,' '); // delimiter
NumToString(stats[i],s);
AccuAddStr(a,s);
}
AccuAddTag(a,GetRString(s,keyword),true);
AccuAddCRLF(a);
}
/************************************************************************
* UpdateFacetime - udpate facetime stats
************************************************************************/
static void UpdateFacetime(void)
{
CheckTimeChange();
UpdateFacetimeLo();
}
/************************************************************************
* UpdateFacetimeLo - udpate facetime stats
************************************************************************/
static void UpdateFacetimeLo(void)
{
long faceTime;
FaceMeasureReport(gFaceMeasure,&faceTime,nil,nil,nil);
if (faceTime > 0)
{
UpdateNumStatLo(kStatFaceTime,faceTime);
UpdateNumStatLo(gFacetimeMode==kFacetimeOther?kStatFaceTimeOther:gFacetimeMode==kFacetimeRead?kStatFaceTimeRead:kStatFaceTimeCompose,faceTime);
}
FaceMeasureReset(gFaceMeasure);
}
/************************************************************************
* SumArray - return sum of long array
************************************************************************/
static long SumArray(long *values,short len)
{
short i;
long sum=0;
for(i=0;i<len;i++)
sum += *values++;
return sum;
}
/************************************************************************
* GetStatTotals - get some totals
************************************************************************/
static void GetStatTotals(NumericStats *stats,StatTimePeriod period,uLong values[],uLong startTime)
{
float dayAve,secs;
secs = LocalDateTime() - startTime;
// average is times 10 so we can do a single decimal place
dayAve = secs ? 10*24.0*60.0*60.0*(float)stats->total/secs : 0 ;
switch (period)
{
case kStatDay:
values[kThisStat] = SumArray(stats->current.day,kDayStatCount);
values[kLastStat] = SumArray(stats->last.day,kDayStatCount);
values[kAveStat] = dayAve;
break;
case kStatWeek:
values[kThisStat] = SumArray(stats->current.week,kWeekStatCount);
values[kLastStat] = SumArray(stats->last.week,kWeekStatCount);
values[kAveStat] = 7.0*dayAve;
break;
case kStatMonth:
values[kThisStat] = SumArray(stats->current.month,kMonthStatCount);
values[kLastStat] = SumArray(stats->last.month,kMonthStatCount);
values[kAveStat] = 30.4375*dayAve;
break;
case kStatYear:
values[kThisStat] = SumArray(stats->current.year,kYearStatCount);
values[kLastStat] = SumArray(stats->last.year,kYearStatCount);
values[kAveStat] = 365.25*dayAve;
break;
}
values[kTotalStat] = stats->total;
}
/************************************************************************
* GetStatTotalPercents - get some totals
************************************************************************/
void GetStatTotalPercents(NumericStats *statNumerator,NumericStats *statDenominator,StatTimePeriod period,uLong values[])
{
switch (period)
{
case kStatDay:
values[kThisStat] = (100*SumArray(statNumerator->current.day,kDayStatCount))/SumArray(statDenominator->current.day,kDayStatCount);
values[kLastStat] = (100*SumArray(statNumerator->last.day,kDayStatCount))/SumArray(statDenominator->last.day,kDayStatCount);
break;
case kStatWeek:
values[kThisStat] = (100*SumArray(statNumerator->current.week,kDayStatCount))/SumArray(statDenominator->current.week,kDayStatCount);
values[kLastStat] = (100*SumArray(statNumerator->last.week,kDayStatCount))/SumArray(statDenominator->last.week,kDayStatCount);
break;
case kStatMonth:
values[kThisStat] = (100*SumArray(statNumerator->current.month,kDayStatCount))/SumArray(statDenominator->current.month,kDayStatCount);
values[kLastStat] = (100*SumArray(statNumerator->last.month,kDayStatCount))/SumArray(statDenominator->last.month,kDayStatCount);
break;
case kStatYear:
values[kThisStat] = (100*SumArray(statNumerator->current.year,kDayStatCount))/SumArray(statDenominator->current.year,kDayStatCount);
values[kLastStat] = (100*SumArray(statNumerator->last.year,kDayStatCount))/SumArray(statDenominator->last.year,kDayStatCount);
break;
}
values[kTotalStat] = (100*statNumerator->total)/statDenominator->total;
}
/************************************************************************
* GetStatsAsText - return stats in HTML format
************************************************************************/
Handle GetStatsAsText(StatTimePeriod period,Boolean extended)
{
Accumulator a;
ComposeStatsData data;
Str255 s;
Str32 sTime;
short rPeriodTab[] = { sStatDay,sStatWeek,sStatMonth,sStatYear };
short rThisTab[] = { sStatToday,sStatThisWeek,sStatThisMonth,sStatThisYear };
short rLastTab[] = { sStatYesterday,sStatLastWeek,sStatLastMonth,sStatLastYear };
uLong secondsTab[] = { 24*60*60,7*24*60*60,31*24*60*60,365*24*60*60 };
Boolean oldShowAve;
Boolean canScoreJunk = CanScoreJunk();
PushGWorld();
data.period = period;
data.rPeriod = StatHTMLStrn+rPeriodTab[period];
data.rThis = StatHTMLStrn+rThisTab[period];
data.rLast = StatHTMLStrn+rLastTab[period];
// Don't display averages until we have statistics for 24 hours
data.showAverage = LocalDateTime() > (*gStatData)->startTime + 60*60*24;
if (AccuInit(&a)) { PopGWorld(); return nil; }
if (!data.showAverage || ((LocalDateTime()-(*gStatData)->startTime) < secondsTab[period])) GetRString(data.sProjected,StatHTMLStrn+sStatProjected);
else data.sProjected[0] = 0;
UpdateCalcStats();
AccuAddRes(&a,StatHTMLStrn+sStatHTMLBegin); // begin HTML
LDRef(gStatData);
if (!extended)
{
ComposeNumStats(&a,kStatReceivedMail,sStatRecdEmail,&data); // Received Email
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
if (canScoreJunk)
{
oldShowAve = data.showAverage; data.showAverage = false;
ComposeNumStats(&a,kStatJunkPercent,sStatJunkPercent,&data); // Junk; calculated
data.showAverage = oldShowAve;
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
}
ComposeNumStats(&a,kStatSentMail,sStatSentEmail,&data); // Sent Email
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
ComposeNumStats(&a,kStatFaceTime,0,&data); // Usage
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
}
else
{
// More stats
ComposeNumStats(&a,kStatReceivedMail,sStatRecdEmail,&data); // Received Email
ComposeShortStats(&a,kStatReadMsg,sStatMsgsRead,&data,StatHTMLStrn+sStatMsgZero,true); // Read mail
ComposeShortStats(&a,kStatReceivedAttach,sStatAttachRecv,&data,StatHTMLStrn+sStatAttachZero,false); // Received attachments
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
if (canScoreJunk)
{
oldShowAve = data.showAverage; data.showAverage = false;
ComposeNumStats(&a,kStatJunkPercent,sStatJunkPercent,&data); // Junk; calculated
data.showAverage = oldShowAve;
ComposeJunkJunk(&a,&data);
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
}
ComposeNumStats(&a,kStatSentMail,sStatSentEmail,&data); // Sent Email
ComposeFRR(&a,&data); // Forward, Reply, Redirect
ComposeShortStats(&a,kStatSentAttach,sStatAttachSent,&data,StatHTMLStrn+sStatAttachZero,false); // Sent attachments
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
ComposeNumStats(&a,kStatFaceTime,0,&data); // Usage
ComposeUsageActivities(&a,&data); // reading, composing, other
AccuAddRes(&a,StatHTMLStrn+sStatDivider); // divider
}
// Since when?
DateString((*gStatData)->startTime,longDate,s,nil);
TimeString((*gStatData)->startTime,false,sTime,nil);
AccuComposeR(&a,StatHTMLStrn+sStatSince,s,sTime);
// Current time
DateString(LocalDateTime(),longDate,s,nil);
TimeString(LocalDateTime(),false,sTime,nil);
AccuComposeR(&a,StatHTMLStrn+sStatCurrentTime,s,sTime);
UL(gStatData);
AccuAddRes(&a,StatHTMLStrn+sStatHTMLEnd); // end HTML
PopGWorld();
return a.data;
}
/************************************************************************
* ComposeFRR - show forward, reply, redirect
************************************************************************/
static void ComposeFRR(AccuPtr a, ComposeStatsData *data)
{
uLong secs;
Str32 sThisVal,sLastVal,sAveVal,sTotalVal;
Str63 sThisPeriod,sLastPeriod;
ShortNumericStats *sStats;
float dayAve;
PicHandle hPict;
GraphData graphData;
SeriesInfo theSeries[3];
StringPtr labels[3];
uLong thisSeries[3],lastSeries[3],aveSeries[3],totalSeries;
short i;
for(i=0;i<3;i++)
{
sStats = &(*gStatData)->shortStats[kStatForwardMsg-kBeginShortStats-1+i];
secs = LocalDateTime() - (*gStatData)->startTime;
dayAve = secs ? 10*24.0*60.0*60.0*(float)sStats->total/secs : 0 ;
switch (data->period)
{
case kStatDay:
thisSeries[i] = sStats->current.day;
lastSeries[i] = sStats->last.day;
aveSeries[i] = dayAve;
break;
case kStatWeek:
thisSeries[i] = sStats->current.week;
lastSeries[i] = sStats->last.week;
aveSeries[i] = dayAve*7.0;
break;
case kStatMonth:
thisSeries[i] = sStats->current.month;
lastSeries[i] = sStats->last.month;
aveSeries[i] = dayAve*30.4375;
break;
case kStatYear:
thisSeries[i] = sStats->current.year;
lastSeries[i] = sStats->last.year;
aveSeries[i] = dayAve*365.25;
break;
}
totalSeries = sStats->total;
RightAlign7(thisSeries[i],sThisVal);
RightAlign7(lastSeries[i],sLastVal);
RightAlign7Dec(aveSeries[i],sAveVal);
if (!data->showAverage)
memset(sAveVal+1,'<EFBFBD>',*sAveVal); // replace with spaces
RightAlign7(totalSeries,sTotalVal);
AccuComposeR(a,StatHTMLStrn+sStatFRR,StatHTMLStrn+sStatForward+i,
sThisVal,WordStrn(StatHTMLStrn+sStatMsgZero,thisSeries[i]),data->rThis,
sLastVal,WordStrn(StatHTMLStrn+sStatMsgZero,lastSeries[i]),data->rLast,
sAveVal,data->rPeriod,data->sProjected,
sTotalVal,WordStrn(StatHTMLStrn+sStatMsgZero,totalSeries));
}
// add a graph
labels[0] = GetRString(sThisPeriod,data->rThis);
labels[1] = GetRString(sLastPeriod,data->rLast);
labels[2] = GetRString(sAveVal,StatHTMLStrn+sStatAverage);
Zero(theSeries);
if (hPict = InitGraphPict(&graphData,theSeries,kStatFaceTime,labels))
{
graphData.dataCount = 3;
graphData.type = kBarGraph;
graphData.xLabels = GetResource('STR#',FRRStrn);
theSeries[0].data = thisSeries;
theSeries[1].data = lastSeries;
theSeries[2].data = aveSeries;
theSeries[2].divider = 10; // increase average graph resolution
if (!CalcElapsedUnits(data->period))
graphData.seriesCount = 2; // not enough data to do average yet
FinishGraphPict(&graphData);
AccuComposeR(a,StatHTMLStrn+sStatPicImage,hPict);
}
}
/************************************************************************
* ComposeJunkJunk - show more junk stats
************************************************************************/
void ComposeJunkJunk(AccuPtr a, ComposeStatsData *data)
{
Str32 sThis, sLast;
uLong junkTotal, total, falseNeg, falsePos, falseWhite;
uLong lastJunkTotal, lastTotal, lastFalseNeg, lastFalsePos, lastFalseWhite;
switch (data->period)
{
case kStatDay:
junkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].current.day);
total = SumArrayHi((*gStatData)->numStats[kStatTotal].current.day);
falseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].current.day);
falsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].current.day);
falseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].current.day);
lastJunkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].last.day);
lastTotal = SumArrayHi((*gStatData)->numStats[kStatTotal].last.day);
lastFalseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].last.day);
lastFalsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].last.day);
lastFalseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].last.day);
break;
case kStatWeek:
junkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].current.week);
total = SumArrayHi((*gStatData)->numStats[kStatTotal].current.week);
falseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].current.week);
falsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].current.week);
falseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].current.week);
lastJunkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].last.week);
lastTotal = SumArrayHi((*gStatData)->numStats[kStatTotal].last.week);
lastFalseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].last.week);
lastFalsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].last.week);
lastFalseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].last.week);
break;
case kStatMonth:
junkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].current.month);
total = SumArrayHi((*gStatData)->numStats[kStatTotal].current.month);
falseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].current.month);
falsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].current.month);
falseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].current.month);
lastJunkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].last.month);
lastTotal = SumArrayHi((*gStatData)->numStats[kStatTotal].last.month);
lastFalseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].last.month);
lastFalsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].last.month);
lastFalseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].last.month);
break;
case kStatYear:
junkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].current.year);
total = SumArrayHi((*gStatData)->numStats[kStatTotal].current.year);
falseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].current.year);
falsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].current.year);
falseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].current.year);
lastJunkTotal = SumArrayHi((*gStatData)->numStats[kStatJunkTotal].last.year);
lastTotal = SumArrayHi((*gStatData)->numStats[kStatTotal].last.year);
lastFalseNeg = SumArrayHi((*gStatData)->numStats[kStatFalseNegatives].last.year);
lastFalsePos = SumArrayHi((*gStatData)->numStats[kStatFalsePositives].last.year);
lastFalseWhite = SumArrayHi((*gStatData)->numStats[kStatFalseWhiteList].last.year);
break;
}
// no divide by zero
if (!total) total = 1;
if (!lastTotal) lastTotal = 1;
AccuComposeR(a,StatHTMLStrn+sStatMsgAndPercent,
StatHTMLStrn+sStatJunkJunk, // Header for section
RightAlign7(junkTotal,sThis),WordStrn(StatHTMLStrn+sStatMsgZero,junkTotal),(100*junkTotal)/total,data->rThis, // this year's info
RightAlign7(lastJunkTotal,sLast),WordStrn(StatHTMLStrn+sStatMsgZero,lastJunkTotal),(100*lastJunkTotal)/lastTotal,data->rLast); // last year's info
AccuComposeR(a,StatHTMLStrn+sStatMsgAndPercent,
StatHTMLStrn+sStatJunkFalseNeg, // Header for section
RightAlign7(falseNeg,sThis),WordStrn(StatHTMLStrn+sStatMsgZero,falseNeg),(100*falseNeg)/total,data->rThis, // this year's info
RightAlign7(lastFalseNeg,sLast),WordStrn(StatHTMLStrn+sStatMsgZero,lastFalseNeg),(100*lastFalseNeg)/lastTotal,data->rLast); // last year's info
AccuComposeR(a,StatHTMLStrn+sStatMsgAndPercent,
StatHTMLStrn+sStatJunkFalsePos, // Header for section
RightAlign7(falsePos,sThis),WordStrn(StatHTMLStrn+sStatMsgZero,falsePos),(100*falsePos)/total,data->rThis, // this year's info
RightAlign7(lastFalsePos,sLast),WordStrn(StatHTMLStrn+sStatMsgZero,lastFalsePos),(100*lastFalsePos)/lastTotal,data->rLast); // last year's info
if (JunkPrefWhiteList())
AccuComposeR(a,StatHTMLStrn+sStatMsgAndPercent,
StatHTMLStrn+sStatJunkFalseWhite, // Header for section
RightAlign7(falseWhite,sThis),WordStrn(StatHTMLStrn+sStatMsgZero,falseWhite),(100*falseWhite)/total,data->rThis, // this year's info
RightAlign7(lastFalseWhite,sLast),WordStrn(StatHTMLStrn+sStatMsgZero,lastFalseWhite),(100*lastFalseWhite)/lastTotal,data->rLast); // last year's info
AccuComposeR(a,StatHTMLStrn+sStatPercent,
StatHTMLStrn+sStatAccuracy, // Header for section
RightAlign7((100*(total-falsePos-falseNeg-falseWhite))/(total-falseWhite),sThis),data->rThis, // this year's info
RightAlign7((100*(lastTotal-lastFalsePos-lastFalseNeg-lastFalseWhite))/(lastTotal-lastFalseWhite),sLast),data->rLast); // last year's info
}
/************************************************************************
* ComposeUsageActivities - show reading, composing, other percentages
************************************************************************/
static void ComposeUsageActivities(AccuPtr a, ComposeStatsData *data)
{
uLong read,compose,other,total,readPercent,composePercent,otherPercent;
Str32 sRead,sCompose,sOther;
PicHandle hPict;
GraphData graphData;
SeriesInfo theSeries[3];
StringPtr labels[3];
uLong series[3];
switch (data->period)
{
case kStatDay:
read = (*gStatData)->shortStats[kStatFaceTimeRead-kBeginShortStats-1].current.day;
compose = (*gStatData)->shortStats[kStatFaceTimeCompose-kBeginShortStats-1].current.day;
other = (*gStatData)->shortStats[kStatFaceTimeOther-kBeginShortStats-1].current.day;
break;
case kStatWeek:
read = (*gStatData)->shortStats[kStatFaceTimeRead-kBeginShortStats-1].current.week;
compose = (*gStatData)->shortStats[kStatFaceTimeCompose-kBeginShortStats-1].current.week;
other = (*gStatData)->shortStats[kStatFaceTimeOther-kBeginShortStats-1].current.week;
break;
case kStatMonth:
read = (*gStatData)->shortStats[kStatFaceTimeRead-kBeginShortStats-1].current.month;
compose = (*gStatData)->shortStats[kStatFaceTimeCompose-kBeginShortStats-1].current.month;
other = (*gStatData)->shortStats[kStatFaceTimeOther-kBeginShortStats-1].current.month;
break;
case kStatYear:
read = (*gStatData)->shortStats[kStatFaceTimeRead-kBeginShortStats-1].current.year;
compose = (*gStatData)->shortStats[kStatFaceTimeCompose-kBeginShortStats-1].current.year;
other = (*gStatData)->shortStats[kStatFaceTimeOther-kBeginShortStats-1].current.year;
break;
}
total = read+compose+other;
readPercent = read*100/total;
composePercent = compose*100/total;
otherPercent = 100 - readPercent - composePercent;
RightAlign7(readPercent,sRead);
RightAlign7(composePercent,sCompose);
RightAlign7(otherPercent,sOther);
AccuComposeR(a,StatHTMLStrn+sStatUsageAct,data->rThis,sRead,sCompose,sOther);
// add a graph
Zero(theSeries);
labels[0] = GetRString(sRead,StatHTMLStrn+sStatReading);
labels[1] = GetRString(sCompose,StatHTMLStrn+sStatComposing);
labels[2] = GetRString(sOther,StatHTMLStrn+sStatOther);
if (hPict = InitGraphPict(&graphData,theSeries,kStatFaceTime,labels))
{
graphData.type = kPieGraph;
graphData.dataCount = 3;
theSeries[0].data = series;
series[0] = readPercent;
series[1] = composePercent;
series[2] = otherPercent;
FinishGraphPict(&graphData);
AccuComposeR(a,StatHTMLStrn+sStatPicImage,hPict);
}
}
/************************************************************************
* ComposeShortStats - compose a set of statistics
************************************************************************/
static void ComposeShortStats(AccuPtr a,StatType type,short strType,ComposeStatsData *data,short rObject,Boolean percent)
{
uLong thisVal,lastVal,aveVal;
Str32 sThisVal,sLastVal,sAveVal,sTotalVal;
float dayAve,secs,recvMailAve;
ShortNumericStats *stats;
NumericStats *recvMailStats;
secs = LocalDateTime() - (*gStatData)->startTime;
// average is times 10 so we can do a single decimal place
stats = &(*gStatData)->shortStats[type-kBeginShortStats-1];
dayAve = secs ? 10*24.0*60.0*60.0*(float)stats->total/secs : 0 ;
if (type==kStatReadMsg)
{
recvMailStats = &(*gStatData)->numStats[kStatReceivedMail];
recvMailAve = secs ? 24.0*60.0*60.0*(float)recvMailStats->total/secs : 0 ;
aveVal = dayAve * 100 / recvMailAve;
}
switch (data->period)
{
case kStatDay:
thisVal = stats->current.day;
lastVal = stats->last.day;
if (percent)
{
thisVal = thisVal * 100 / SumArray(recvMailStats->current.day,kDayStatCount);
lastVal = lastVal * 100 / SumArray(recvMailStats->last.day,kDayStatCount);
}
else
aveVal = dayAve;
break;
case kStatWeek:
thisVal = stats->current.week;
lastVal = stats->last.week;
if (percent)
{
thisVal = thisVal * 100 / SumArray(recvMailStats->current.week,kWeekStatCount);
lastVal = lastVal * 100 / SumArray(recvMailStats->last.week,kWeekStatCount);
}
else
aveVal = 7.0*dayAve;
break;
case kStatMonth:
thisVal = stats->current.month;
lastVal = stats->last.month;
if (percent)
{
thisVal = thisVal * 100 / SumArray(recvMailStats->current.month,kMonthStatCount);
lastVal = lastVal * 100 / SumArray(recvMailStats->last.month,kMonthStatCount);
}
else
aveVal = 30.4375*dayAve;
break;
case kStatYear:
thisVal = stats->current.year;
lastVal = stats->last.year;
if (percent)
{
thisVal = thisVal * 100 / SumArray(recvMailStats->current.year,kYearStatCount);
lastVal = lastVal * 100 / SumArray(recvMailStats->last.year,kYearStatCount);
}
else
aveVal = 365.25*dayAve;
break;
}
if (percent)
{
// make sure average values don't exceen 100%
if (thisVal > 100) thisVal = 100;
if (lastVal > 100) lastVal = 100;
if (aveVal > 1000) aveVal = 1000; // average is times 10
}
RightAlign7(thisVal,sThisVal);
RightAlign7(lastVal,sLastVal);
RightAlign7Dec(aveVal,sAveVal);
if (!data->showAverage)
memset(sAveVal+1,'<EFBFBD>',*sAveVal); // replace with spaces
RightAlign7(stats->total,sTotalVal);
if (percent)
{
AccuComposeR(a,StatHTMLStrn+strType,
sThisVal,WordStrn(rObject,percent?2:thisVal),data->rThis,
sLastVal,WordStrn(rObject,percent?2:lastVal),data->rLast,
sAveVal,
sTotalVal,WordStrn(rObject,stats->total));
}
else
{
AccuComposeR(a,StatHTMLStrn+strType,
sThisVal,WordStrn(rObject,percent?2:thisVal),data->rThis,
sLastVal,WordStrn(rObject,percent?2:lastVal),data->rLast,
sAveVal,data->rPeriod,data->sProjected,
sTotalVal,WordStrn(rObject,stats->total));
}
}
/************************************************************************
* ComposeNumStats - compose a set of statistics
************************************************************************/
static void ComposeNumStats(AccuPtr a,StatType type,short strType,ComposeStatsData *data)
{
uLong values[4];
PicHandle hPict;
Str32 sThisVal,sLastVal,sAveVal,sTotalVal;
Str63 sThisPeriod,sLastPeriod;
if (type==kStatJunkPercent)
GetStatTotalPercents(&(*gStatData)->numStats[kStatJunkTotal],&(*gStatData)->numStats[kStatTotal],data->period,values);
else
GetStatTotals(&(*gStatData)->numStats[type],data->period,values,type!=kStatJunkPercent?(*gStatData)->startTime:(*gStatData)->junkStartTime);
if (type == kStatFaceTime)
{
// Usage
RightAlign7Dec(values[kThisStat]/360,sThisVal);
RightAlign7Dec(values[kLastStat]/360,sLastVal);
RightAlign7Dec(values[kAveStat]/3600,sAveVal);
if (!data->showAverage)
memset(sAveVal+1,'<EFBFBD>',*sAveVal); // replace with spaces
RightAlign7Dec(values[kTotalStat]/360,sTotalVal);
AccuComposeR(a,StatHTMLStrn+sStatUsageEntry,
sThisVal,data->rThis,
sLastVal,data->rLast,
sAveVal,data->rPeriod,data->sProjected,
sTotalVal);
}
else
{
short fmt = data->showAverage ? sStatMsgsEntry : sStatNoAveEntry;
short unit = type==kStatJunkPercent ? sStatPercentZero : sStatMsgZero;
RightAlign7(values[kThisStat],sThisVal);
RightAlign7(values[kLastStat],sLastVal);
RightAlign7Dec(values[kAveStat],sAveVal);
RightAlign7(values[kTotalStat],sTotalVal);
AccuComposeR(a,StatHTMLStrn+fmt,StatHTMLStrn+strType,
sThisVal,WordStrn(StatHTMLStrn+unit,values[kThisStat]),data->rThis,
sLastVal,WordStrn(StatHTMLStrn+unit,values[kLastStat]),data->rLast,
sAveVal,data->rPeriod,data->sProjected,
sTotalVal,WordStrn(StatHTMLStrn+unit,values[kTotalStat]));
}
// add a graph
hPict = GetGraphPict(&(*gStatData)->numStats[type],data->period,type == kStatFaceTime,data->showAverage,
GetRString(sThisPeriod,data->rThis),GetRString(sLastPeriod,data->rLast),GetRString(sAveVal,StatHTMLStrn+sStatAverage));
if (hPict)
AccuComposeR(a,StatHTMLStrn+sStatPicImage,hPict);
}
/************************************************************************
* WordStrn - return correct singular or plural form of string resource
************************************************************************/
static short WordStrn(short rStrn,long value)
{
if (value==0) return rStrn;
else if (value==1) return rStrn+1;
else return rStrn+2;
}
/************************************************************************
* RightAlign7 - right align with 7 chars in field
************************************************************************/
static UPtr RightAlign7(uLong value,UPtr s)
{
return RightAlign(value,s,7,0);
}
/************************************************************************
* RightAlign7Dec - right align with 7 chars in field and use decimal place
************************************************************************/
static UPtr RightAlign7Dec(uLong value,UPtr s)
{
return RightAlign(value,s,7,1);
}
/************************************************************************
* RightAlign - right align with X chars in field, and add any commas
************************************************************************/
static UPtr RightAlign(uLong value,UPtr s,short minLen,short decPlace)
{
short len;
char cTokThousands, cDecPoint;
if (decPlace && value >= 100)
{
// number is big enough we aren't going to worry about decimal
while (decPlace>0)
{
value /= 10;
decPlace--;
}
}
NumToString(value,s);
if (decPlace && (cDecPoint = GetIntlNumberPart(tokDecPoint)))
{
if (*s==1)
PInsertC(s,sizeof(s),'0',s+1);
PInsertC(s,sizeof(s),cDecPoint,s+*s-decPlace+1);
}
// Put in some commas if large number
len = *s;
if (cTokThousands = GetIntlNumberPart(tokThousands))
{
if (len > 9) PInsertC(s,32,cTokThousands,s+*s-8);
if (len > 6) PInsertC(s,32,cTokThousands,s+*s-5);
if (len > 3) PInsertC(s,32,cTokThousands,s+*s-2);
}
while (*s < minLen)
PInsertC(s,32,'<EFBFBD>',s+1);
return s;
}
/************************************************************************
* GetGraphPict - make a graph
************************************************************************/
static PicHandle GetGraphPict(NumericStats *stats,StatTimePeriod period,Boolean seconds,Boolean showAverage,StringPtr sThis,StringPtr sLast,StringPtr sAverage)
{
uLong aveVals[31],*aveRaw;
short i;
GraphData data;
SeriesInfo theSeries[3];
StringPtr labels[3];
DateTimeRec dtStart,dtNow;
uLong elapsedUnits;
Boolean doAverage = true;
short compare1;
PicHandle hPic;
labels[0] = sThis;
labels[1] = sLast;
labels[2] = sAverage;
if (hPic = InitGraphPict(&data,theSeries,period,labels))
{
data.xLabels = GetXLabels(period);
if (seconds)
{
data.divider = 360; // 60*60/10
data.decPlace = 1;
}
theSeries[0].type = GetRLong(STAT_CURRENT_TYPE);
theSeries[1].type = GetRLong(STAT_PREVIOUS_TYPE);
theSeries[2].type = GetRLong(STAT_AVERAGE_TYPE);
SecondsToDate(LocalDateTime(),&dtNow);
SecondsToDate((*gStatData)->startTime,&dtStart);
dtNow.minute = dtNow.second = 0;
dtStart.minute = dtStart.second = 0;
elapsedUnits = CalcElapsedUnits(period);
switch (period)
{
case kStatDay:
theSeries[0].data = stats->current.day;
theSeries[1].data = stats->last.day;
aveRaw = stats->average.day;
compare1 = dtStart.hour+1;
theSeries[0].dataCount = dtNow.hour+1;
break;
case kStatWeek:
theSeries[0].data = stats->current.week;
theSeries[1].data = stats->last.week;
aveRaw = stats->average.week;
compare1 = dtStart.dayOfWeek-1;
theSeries[0].dataCount = dtNow.dayOfWeek;
break;
case kStatMonth:
theSeries[0].data = stats->current.month;
theSeries[1].data = stats->last.month;
aveRaw = stats->average.month;
compare1 = dtStart.day-1;
theSeries[0].dataCount = dtNow.day;
break;
case kStatYear:
theSeries[0].data = stats->current.year;
theSeries[1].data = stats->last.year;
aveRaw = stats->average.year;
compare1 = dtStart.month-1;
theSeries[0].dataCount = dtNow.month;
break;
}
// calculate averages
if (elapsedUnits && showAverage)
{
short divider;
for(i=0;i<dataCountTab[period];i++)
{
divider = elapsedUnits;
if (i < compare1 && divider>1) divider--;
aveVals[i] = (float)aveRaw[i]*10.0/divider;
}
theSeries[2].data = aveVals;
theSeries[2].divider = 10; // increase graph resolution
}
else
data.seriesCount = 2; // not enough data to do average yet
FinishGraphPict(&data);
}
return hPic;
}
/************************************************************************
* CalcElapsedUnits - how many time periods since beginning of stats?
************************************************************************/
static uLong CalcElapsedUnits(StatTimePeriod period)
{
uLong startSecs,nowSecs,elapsedSecs;
DateTimeRec dtStart,dtNow;
uLong elapsedUnits;
SecondsToDate(LocalDateTime(),&dtNow);
SecondsToDate((*gStatData)->startTime,&dtStart);
dtNow.minute = dtNow.second = 0;
dtStart.minute = dtStart.second = 0;
switch (period)
{
case kStatDay:
dtStart.hour++;
DateToSeconds(&dtStart,&startSecs);
DateToSeconds(&dtNow,&nowSecs);
elapsedSecs = nowSecs > startSecs ? nowSecs - startSecs : 0;
elapsedUnits = elapsedSecs/(24*60*60);
break;
case kStatWeek:
dtNow.hour = dtStart.hour = 0;
dtStart.day++;
DateToSeconds(&dtStart,&startSecs);
DateToSeconds(&dtNow,&nowSecs);
elapsedSecs = nowSecs > startSecs ? nowSecs - startSecs : 0;
elapsedUnits = elapsedSecs/(7*24*60*60);
break;
case kStatMonth:
elapsedUnits = 12*(dtNow.year-dtStart.year);
dtStart.year = dtNow.year;
if (TimeCompare(&dtNow,&dtStart)<0)
{
elapsedUnits -= 12;
dtNow.month += 12;
}
if (dtNow.month != dtStart.month)
elapsedUnits += dtNow.month - dtStart.month - 1;
break;
case kStatYear:
elapsedUnits = dtNow.year-dtStart.year;
dtStart.year = dtNow.year;
if (TimeCompare(&dtNow,&dtStart)<0)
elapsedUnits--;
break;
}
return elapsedUnits;
}
/************************************************************************
* InitGraphPict - make a graph
************************************************************************/
static PicHandle InitGraphPict(GraphData *data,SeriesInfo *theSeries,StatTimePeriod period,StringPtr *labels)
{
PicHandle hPic = nil;
OpenCPicParams picParams;
MyWindowPtr statWin;
// Need a window to use for drawing the picture
// The stat window should be available
if (statWin = GetStatWin())
{
Zero(picParams);
SetRect(&picParams.srcRect,0,0,GetRLong(STAT_GRAPH_WIDTH),GetRLong(STAT_GRAPH_HEIGHT));
picParams.hRes = 0x00480000;
picParams.vRes = 0x00480000;
picParams.version = -2;
SetPort_(GetMyWindowCGrafPtr(statWin));
if (hPic = OpenCPicture(&picParams))
{
Zero(*data);
SetRect(&data->bounds,0,0,GetRLong(STAT_GRAPH_WIDTH),GetRLong(STAT_GRAPH_HEIGHT));
ClipRect(&data->bounds);
data->type = kLineGraph;
data->seriesCount = 3;
data->dataCount = dataCountTab[period];
data->series = theSeries;
data->xLabels = GetXLabels(period);
data->legend = 1;
GetRColor(&theSeries[0].color,STAT_CURRENT_COLOR);
theSeries[0].dataCount = data->dataCount;
theSeries[0].label = labels[0];
theSeries[0].divider = 0;
GetRColor(&theSeries[1].color,STAT_PREVIOUS_COLOR);
theSeries[1].dataCount = data->dataCount;
theSeries[1].label = labels[1];
theSeries[1].divider = 0;
GetRColor(&theSeries[2].color,STAT_AVERAGE_COLOR);
theSeries[2].dataCount = data->dataCount;
theSeries[2].label = labels[2];
theSeries[2].divider = 0;
if (period==kStatDay)
data->largeTickInterval = 4;
else if (period==kStatMonth)
data->largeTickInterval = 5;
}
}
return hPic;
}
/************************************************************************
* FinishGraphPict - finish up graph PICT
************************************************************************/
static void FinishGraphPict(GraphData *data)
{
DrawGraph(data);
ClosePicture();
}
/************************************************************************
* GetXLabels - generate the X-axis labels
************************************************************************/
static Handle GetXLabels(StatTimePeriod period)
{
static Handle labelList[4];
Str255 s;
OSErr err = noErr;
if (!labelList[period] || !*labelList[period])
{
// need to generate stringlist
Accumulator a;
short i;
switch (period)
{
case kStatDay:
labelList[period] = GetResource('STR#',StatHoursStrn);
break;
case kStatMonth:
if (AccuInit(&a)) return nil;
AccuAddPtr(&a,dataCountTab+period,sizeof(short)); // count
for(i=1;i<=dataCountTab[period];i++)
{
if (i%5) // display every 5th value
*s = 0;
else
NumToString(i,s);
if (err = AccuAddPtr(&a,&s,*s+1)) break;
}
if (err)
AccuZap(a);
else
{
AccuTrim(&a);
labelList[period] = a.data;
}
break;
case kStatWeek:
case kStatYear:
// This is Pete's suggested way to get these
// Get short names for days of week or months
GetAbbrevNames(&labelList[kStatYear],&labelList[kStatWeek]);
break;
}
}
return labelList[period];
}
/************************************************************************
* GetAbbrevNames - get abbreviated month and weekday names
************************************************************************/
static OSErr GetAbbrevNames(Handle *monthNames, Handle *dayNames)
{
Itl1ExtRec **iHndl;
Boolean hasLocal, hasMonths, hasDays;
long monthsLen, daysLen;
if((iHndl = (Itl1ExtRec **)GetIntlResource(1)) == nil)
return memFullErr;
hasLocal = ((**iHndl).base.localRtn[0] == 0xA89F);
hasMonths = hasLocal && ((**iHndl).abbrevMonthsTableOffset != 0) && ((**iHndl).abbrevMonthsTableLength != 0);
hasDays = hasLocal && ((**iHndl).abbrevDaysTableOffset != 0) && ((**iHndl).abbrevDaysTableLength != 0);
monthsLen = hasMonths ? (**iHndl).abbrevMonthsTableLength : ((**iHndl).base.abbrLen + 1) * 12;
daysLen = hasDays ? (**iHndl).abbrevDaysTableLength : ((**iHndl).base.abbrLen + 1) * 7;
*monthNames = NuHTempBetter(monthsLen+sizeof(short));
*dayNames = NuHTempBetter(daysLen+sizeof(short));
if((*monthNames == nil) || (*dayNames == nil))
{
ZapHandle(*monthNames);
return memFullErr;
}
if(hasMonths)
{
BMD(&(**iHndl).base + (**iHndl).abbrevMonthsTableOffset, **monthNames, (**iHndl).abbrevMonthsTableLength);
}
else
{
*(short *)**monthNames = 12;
MoveNames(&(**iHndl).base.months, 12, (**iHndl).base.abbrLen, **monthNames + 2);
}
if(hasDays)
{
BMD(&(**iHndl).base + (**iHndl).abbrevDaysTableOffset, **dayNames, (**iHndl).abbrevDaysTableLength);
}
else
{
*(short *)**dayNames = 7;
MoveNames(&(**iHndl).base.days, 7, (**iHndl).base.abbrLen, **dayNames + 2);
}
return noErr;
}
/************************************************************************
* MoveNames - move names from array to STR# format
************************************************************************/
static void MoveNames(Str15 *names, long count, long abbrLen, StringPtr dest)
{
short srcIdx;
for(srcIdx=0;srcIdx<count;srcIdx++)
{
StringPtr src;
long i,srcLen;
src = names[srcIdx];
srcLen = *src++;
i = MIN(abbrLen,srcLen);
*dest++ = i;
while(i--)
*dest++ = *src++;
}
}
/************************************************************************
* SaveStats - save stats to file in XML format
************************************************************************/
static void ParseStatFile(Handle hStatXML)
{
#define kMaxTokenStack 64
TokenInfo tokenInfo;
short tokenType;
Str255 sToken;
short tokenStack[kMaxTokenStack];
StatDataPtr pStatData;
typedef enum { kFillModeNone,kFillModeNumStat,kFillModeShortStat } FillMode;
FillMode mode = kFillModeNone;
ShortNumericStats *pShortStats = nil;
ShortPeriodicStats *pShortPeriodic = nil;
long *pShortValue = nil;
NumericStats *pNumStats = nil;
PeriodicStats *pNumPeriodic = nil;
long *pNumValue = nil;
short maxArraySize = 0;
Boolean validDocEntry = false;
short stackIdx = -1,i;
Accumulator accuKeywordList;
Handle hKeywords;
// Initialize tokenizer
tokenInfo.pText = LDRef(hStatXML);
tokenInfo.size = GetHandleSize(hStatXML);
tokenInfo.offset = 0;
tokenInfo.aKeywords = &accuKeywordList;
hKeywords = GetResource('STR#',StatXMLStrn);
if (!hKeywords) return;
DetachResource(hKeywords); // We may be changing this handle, so detach it
AccuInitWithHandle(&accuKeywordList,hKeywords);
pStatData = LDRef(gStatData);
// Parse loop
while ((tokenType = GetNextToken(&tokenInfo)) != kTokenDone)
{
TokenToString(&tokenInfo, sToken);
if (tokenType == kContent)
{
// Get element content
long zone;
if (validDocEntry)
{
switch (mode)
{
case kFillModeNone:
// not filling any stats right now
switch (tokenStack[stackIdx])
{
case ksStatStartTime:
pStatData->startTime = BeautifyDate(sToken,&zone); pStatData->startTime+=zone; break;
case ksStatCurrentTime:
pStatData->currentTime = BeautifyDate(sToken,&zone); pStatData->currentTime+=zone; break;
case ksStatJunkStartTime:
pStatData->junkStartTime = BeautifyDate(sToken,&zone); pStatData->junkStartTime+=zone; break;
}
break;
case kFillModeNumStat:
// filling number stat
if (pNumValue && maxArraySize)
{
// array of data, scan for each data item
if (tokenInfo.tokenLen)
{
long saveOffset = tokenInfo.offset;
long saveSize = tokenInfo.size;
unsigned char c;
// Redirect token info to look at attributes
tokenInfo.offset = tokenInfo.tokenStart;
tokenInfo.size = tokenInfo.tokenStart+tokenInfo.tokenLen;
for(i=0;i<maxArraySize;i++)
{
c = SkipWhiteSpace(&tokenInfo);
tokenInfo.tokenStart = tokenInfo.offset-1;
while (c && c != '<' && c != ',' && !IsWhiteSpace(c))
c = GetNextTokenChar(&tokenInfo);
tokenInfo.tokenLen = tokenInfo.offset-tokenInfo.tokenStart;
if (c) tokenInfo.tokenLen--;
TokenToString(&tokenInfo,sToken);
if (!*sToken) break;
StringToNum(sToken,pNumValue+i);
if (!c) break;
}
// Restore info
tokenInfo.offset = saveOffset;
tokenInfo.size = saveSize;
}
pNumValue = nil;
maxArraySize = 0;
}
else if (pNumStats && tokenStack[stackIdx]==ksStatTotal)
pNumStats->total = PStrToNum(sToken);
break;
case kFillModeShortStat:
// filling short stat
if (pShortValue)
*pShortValue = PStrToNum(sToken);
else if (pShortStats && tokenStack[stackIdx]==ksStatTotal)
pShortStats->total = PStrToNum(sToken);
break;
}
}
}
else
{
// Process a tag
short tokenIdx;
tokenIdx = GetTokenIdx(tokenInfo.aKeywords,sToken);
switch (tokenType)
{
case kElementTag:
switch (tokenIdx)
{
case ksStatDocEntry:
validDocEntry = true; break;
case ksStatReceivedMail:
pNumStats = &pStatData->numStats[kStatReceivedMail]; mode=kFillModeNumStat; break;
case ksStatSentMail:
pNumStats = &pStatData->numStats[kStatSentMail]; mode=kFillModeNumStat; break;
case ksStatFaceTime:
pNumStats = &pStatData->numStats[kStatFaceTime]; mode=kFillModeNumStat; break;
case ksStatScoredJunk:
pNumStats = &pStatData->numStats[kStatScoredJunk]; mode=kFillModeNumStat; break;
case ksStatScoredNotJunk:
pNumStats = &pStatData->numStats[kStatScoredNotJunk]; mode=kFillModeNumStat; break;
case ksStatWhiteList:
pNumStats = &pStatData->numStats[kStatWhiteList]; mode=kFillModeNumStat; break;
case ksStatFalsePositives:
pNumStats = &pStatData->numStats[kStatFalsePositives]; mode=kFillModeNumStat; break;
case ksStatFalseNegatives:
pNumStats = &pStatData->numStats[kStatFalseNegatives]; mode=kFillModeNumStat; break;
case ksStatFalseWhiteList:
pNumStats = &pStatData->numStats[kStatFalseWhiteList]; mode=kFillModeNumStat; break;
case ksStatReceivedAttach:
pShortStats = &pStatData->shortStats[kStatReceivedAttach-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatSentAttach:
pShortStats = &pStatData->shortStats[kStatSentAttach-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatReadMsg:
pShortStats = &pStatData->shortStats[kStatReadMsg-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatForwardMsg:
pShortStats = &pStatData->shortStats[kStatForwardMsg-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatReplyMsg:
pShortStats = &pStatData->shortStats[kStatReplyMsg-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatRedirectMsg:
pShortStats = &pStatData->shortStats[kStatRedirectMsg-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatFaceTimeRead:
pShortStats = &pStatData->shortStats[kStatFaceTimeRead-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatFaceTimeCompose:
pShortStats = &pStatData->shortStats[kStatFaceTimeCompose-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatFaceTimeOther:
pShortStats = &pStatData->shortStats[kStatFaceTimeOther-kBeginShortStats-1]; mode=kFillModeShortStat; break;
case ksStatCurrent:
if (pNumStats) pNumPeriodic = &pNumStats->current;
else if (pShortStats) pShortPeriodic = &pShortStats->current;
break;
case ksStatPrevious:
if (pNumStats) pNumPeriodic = &pNumStats->last;
else if (pShortStats) pShortPeriodic = &pShortStats->last;
break;
case ksStatAveSum:
if (pNumStats) pNumPeriodic = &pNumStats->average;
else if (pShortStats) pShortPeriodic = &pShortStats->average;
break;
// case ksStatTotal:
case ksStatDay:
if (pNumPeriodic) { pNumValue = &pNumPeriodic->day; maxArraySize = kDayStatCount; }
else if (pShortPeriodic) pShortValue = &pShortPeriodic->day;
break;
case ksStatWeek:
if (pNumPeriodic) { pNumValue = &pNumPeriodic->week; maxArraySize = kWeekStatCount; }
else if (pShortPeriodic) pShortValue = &pShortPeriodic->week;
break;
case ksStatMonth:
if (pNumPeriodic) { pNumValue = &pNumPeriodic->month; maxArraySize = kMonthStatCount; }
else if (pShortPeriodic) pShortValue = &pShortPeriodic->month;
break;
case ksStatYear:
if (pNumPeriodic) { pNumValue = &pNumPeriodic->year; maxArraySize = kYearStatCount; }
else if (pShortPeriodic) pShortValue = &pShortPeriodic->year;
break;
}
// Push token on stack
if (stackIdx < kMaxTokenStack-1)
tokenStack[++stackIdx] = tokenIdx;
break;
case kEndTag:
// End of element. Pop off stack. Actually search whole stack
// in case document is not well-formed.
while (stackIdx >= 0)
{
//
if (tokenStack[stackIdx--] == tokenIdx)
break;
}
switch (tokenIdx)
{
case ksStatDocEntry:
validDocEntry = false; break;
case ksStatReceivedMail:
case ksStatSentMail:
case ksStatFaceTime:
case ksStatScoredJunk:
case ksStatScoredNotJunk:
case ksStatWhiteList:
case ksStatFalsePositives:
case ksStatFalseNegatives:
case ksStatFalseWhiteList:
case ksStatReceivedAttach:
case ksStatSentAttach:
case ksStatReadMsg:
case ksStatForwardMsg:
case ksStatReplyMsg:
case ksStatRedirectMsg:
case ksStatFaceTimeRead:
case ksStatFaceTimeCompose:
case ksStatFaceTimeOther:
pShortStats = nil;
pNumStats = nil;
mode = kFillModeNone; break;
case ksStatCurrent:
case ksStatPrevious:
case ksStatAveSum:
pNumPeriodic = nil; pShortPeriodic = nil; break;
// case ksStatTotal:
case ksStatDay:
case ksStatWeek:
case ksStatMonth:
case ksStatYear:
pNumValue = nil; pShortValue = nil; maxArraySize = 0; break;
}
break;
case kEmptyElementTag:
// shouldn't have any of these!
break;
}
}
}
AccuZap(accuKeywordList);
UL(gStatData);
}
/************************************************************************
* GetIntlNumberPart - get an international number part
************************************************************************/
char GetIntlNumberPart(short charToken)
{
Handle itlHandle;
long offset,length;
char c = 0;
GetIntlResourceTable(smSystemScript,smNumberPartsTable,&itlHandle,&offset,&length);
if (itlHandle)
{
NumberPartsPtr pNumParts = (NumberPartsPtr)(*itlHandle + offset);
c = pNumParts->data[charToken-1].a[1];
}
else
{
// can't get number part table for some reason. use U.S. defaults
switch (charToken)
{
case tokDecPoint: c = '.'; break;
case tokThousands: c = ','; break;
}
}
return c;
}
/************************************************************************
* UpdateCalcStats - update calculated status
************************************************************************/
void UpdateCalcStats(void)
{
long oldTotal;
// Take the ones we score as junk...
(*gStatData)->numStats[kStatJunkTotal] = (*gStatData)->numStats[kStatScoredJunk];
// Subtract the false positives (they weren't really junk)
NumericStatOperation(kStatJunkTotal,kStatFalsePositives,opMinus);
// Add the false negatives (they were junk, but the scoring missed them)
NumericStatOperation(kStatJunkTotal,kStatFalseNegatives,opPlus);
// Add the junk that was on the white list and so didn't get recognized as junk
NumericStatOperation(kStatJunkTotal,kStatFalseWhiteList,opPlus);
// the result is all the junk we got!
// Take the ones we didn't score as junk...
(*gStatData)->numStats[kStatTotal] = (*gStatData)->numStats[kStatScoredNotJunk];
// Add the whitelist
NumericStatOperation(kStatTotal,kStatWhiteList,opPlus);
// Add the scored junk
NumericStatOperation(kStatTotal,kStatScoredJunk,opPlus);
// The result is all eligible mail
// Compute percentages
(*gStatData)->numStats[kStatJunkPercent] = (*gStatData)->numStats[kStatJunkTotal];
oldTotal = (*gStatData)->numStats[kStatJunkPercent].total;
NumericStatOperation(kStatJunkPercent,kStatTotal,opPercent);
(*gStatData)->numStats[kStatJunkPercent].total = oldTotal;
}
/************************************************************************
* NumericStatOperation - add or subtract one numeric stat from another
************************************************************************/
void NumericStatOperation(StatType destination,StatType increment,OpType op)
{
PeriodicStatOperation(&(*gStatData)->numStats[destination].current,&(*gStatData)->numStats[increment].current,op);
PeriodicStatOperation(&(*gStatData)->numStats[destination].last,&(*gStatData)->numStats[increment].last,op);
PeriodicStatOperation(&(*gStatData)->numStats[destination].average,&(*gStatData)->numStats[increment].average,op);
switch (op)
{
case opPlus:
(*gStatData)->numStats[destination].total += (*gStatData)->numStats[increment].total;
break;
case opMinus:
(*gStatData)->numStats[destination].total -= (*gStatData)->numStats[increment].total;
break;
case opPercent:
(*gStatData)->numStats[destination].total = (*gStatData)->numStats[increment].total ?
(100*(*gStatData)->numStats[destination].total)/(*gStatData)->numStats[increment].total
: 0;
break;
}
}
/************************************************************************
* PeriodicStatOperation - add or subtract one periodic stat from another
************************************************************************/
void PeriodicStatOperation(PeriodicStats *destP, PeriodicStats *incP, OpType op)
{
LongArrayOperation(destP->day,incP->day,sizeof(incP->day)/sizeof(incP->day[0]),op);
LongArrayOperation(destP->week,incP->week,sizeof(incP->week)/sizeof(incP->week[0]),op);
LongArrayOperation(destP->month,incP->month,sizeof(incP->month)/sizeof(incP->month[0]),op);
LongArrayOperation(destP->year,incP->year,sizeof(incP->year)/sizeof(incP->year[0]),op);
}
/************************************************************************
* LongArrayOperation - add or subtract one array of longs from another
************************************************************************/
void LongArrayOperation(long *array,long *sub,short n,OpType op)
{
switch (op)
{
case opPlus:
while (n-->0)
*array++ += *sub++;
break;
case opMinus:
while (n-->0)
*array++ -= *sub++;
break;
case opPercent:
for (;n-->0;array++,sub++)
*array = (100 * (*array))/(*sub);
break;
}
}