1 line
73 KiB
C
Executable File
1 line
73 KiB
C
Executable File
/* 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;
|
||
}
|
||
} |