//*** CUACount -- user-assistance counter w/ decay // NOTES // todo: scavenging to clean out registry. but see caveats in UAC_CDEF. #include "priv.h" #include "uacount.h" #include "uareg.h" #define DM_UEMTRACE TF_UEM #define MAX(a, b) (((a) > (b)) ? (a) : (b)) //*** UAC_CDEFAULT -- initial _cCnt for entry (we *always* show items) // NOTES // eventually we might want to scavenge all entries, decaying them down // and deleting any that decay to 0. note however that this will cause // them to look like they have a default count of 1 (see CUAC::Init), so // they'll suddenly appear on the menus again. #define UAC_CDEFAULT 0 // initial _cCnt for entry #define SID_SDEFAULT SID_SNOWREAD // initial _sidMru for new entry //*** // NOTES // it's getting to the point that we should disallow stack-alloc'ed // guys and instead count on new() to 0-init us. CUACount::CUACount() { // Since this is created on the stack, we don't get the benefits of the // Heap allocator's zero initialization... ZeroMemory(_GetRawData(), _GetRawCount()); _fInited = FALSE; // need to call Initialize #if XXX_VERSIONED _cbSize = -1; #endif #if XXX_DELETE _fInherited = FALSE; #endif _fDirty = FALSE; _fNoDecay = _fNoPurge = FALSE; return; } #ifdef DEBUG BOOL CUACount::DBIsInit() { #if XXX_VERSIONED ASSERT((_cbSize == SIZEOF(SUACount)) == BOOLIFY(_fInited)); #endif return _fInited; } #endif HRESULT CUACount::Initialize(IUASession *puas) { _puas = puas; if (!_fInited) { _fInited = TRUE; #if XXX_VERSIONED // todo: _cbSize -1 means no entry, < SIZEOF means version upgrade _cbSize = SIZEOF(SUACount); #endif // hardcode the SZ_CUACount_ctor values here _cCnt = UAC_CDEFAULT; // all items start out visible _sidMruDisk = SID_SNOWREAD; // ... and non-aged } _sidMru = _sidMruDisk; if (ISSID_SSPECIAL(_sidMruDisk)) { _sidMru = _ExpandSpecial(_sidMruDisk); if (_sidMruDisk == SID_SNOWINIT) { _sidMruDisk = _sidMru; _fDirty = TRUE; } else if (_sidMruDisk == SID_SNOWREAD) { _sidMruDisk = _sidMru; ASSERT(!_fDirty); } } return S_OK; } HRESULT CUACount::LoadFrom(PFNNRW3 pfnIO, PNRWINFO pRwi) { HRESULT hr; hr = (*pfnIO->_pfnRead)(_GetRawData(), _GetRawCount(), pRwi); if (SUCCEEDED(hr)) _fInited = TRUE; return hr; } HRESULT CUACount::SaveTo(BOOL fForce, PFNNRW3 pfnIO, PNRWINFO pRwi) { HRESULT hr; hr = S_FALSE; if (fForce || _fDirty) { if (!ISSID_SSPECIAL(_sidMruDisk)) _sidMruDisk = _sidMru; #if XXX_DELETE if (_cCnt == 0 && !_fNoPurge && pfnIO->_pfnDelete) hr = (*pfnIO->_pfnDelete)(_GetRawData(), _GetRawCount(), pRwi); else #endif hr = (*pfnIO->_pfnWrite)(_GetRawData(), _GetRawCount(), pRwi); // ASSERT(SUCCEEDED(hr)); // this legitimately happens (low memory, access denied) _fDirty = FALSE; } return hr; } //*** GetCount -- get count info (w/ lazy decay) // int CUACount::GetCount() { ASSERT(DBIsInit()); int cCnt = _DecayCount(FALSE); return cCnt; } void CUACount::IncCount() { AddCount(1); return; } void CUACount::AddCount(int i) { ASSERT(DBIsInit()); _DecayCount(TRUE); _cCnt += i; if (_cCnt == 0 && i > 0) { // nt5:173048 // handle wrap // should never happen, but what the heck // do *not* remove this assert, if we ever let people do DecCount // we'll need to rethink it... ASSERT(0); // 'impossible' _cCnt++; } // 981029 new incr algorithm per ie5 PM // UAC_MINCOUNT: initial inc starts at 6 // _fNoDecay: but, UAssist2 doesn't do this if (_cCnt < UAC_MINCOUNT && !_fNoDecay) _cCnt = UAC_MINCOUNT; return; } //*** // NOTES // should we update the timestamp? maybe add a fMru param? void CUACount::SetCount(int cCnt) { ASSERT(DBIsInit()); _cCnt = cCnt; return; } void CUACount::SetFileTime(const FILETIME *pft) { ASSERT(DBIsInit()); _ftExecuteTime = *pft; return; } #if XXX_DELETE #define BTOM(b, m) ((b) ? (m) : 0) DWORD CUACount::_SetFlags(DWORD dwMask, DWORD dwFlags) { // standard guys if (dwMask & UAXF_NOPURGE) _fNoPurge = BOOLIFY(dwFlags & UAXF_NOPURGE); #if 0 if (dwMask & UAXF_BACKUP) _fBackup = BOOLIFY(dwFlags & UAXF_BACKUP); #endif if (dwMask & UAXF_NODECAY) _fNoDecay = BOOLIFY(dwFlags & UAXF_NODECAY); // my guys if (dwMask & UACF_INHERITED) _fInherited = BOOLIFY(dwFlags & UACF_INHERITED); return 0 // n.b. see continuation line(s)!!! #if XXX_DELETE | BTOM(_fInherited, UACF_INHERITED) #endif | BTOM(_fNoPurge, UAXF_NOPURGE) | BTOM(_fNoDecay, UAXF_NODECAY) ; } #endif //*** PCTOF -- p% of n (w/o floating point!) // #define PCTOF(n, p) (((n) * (p)) / 100) //*** _DecayCount -- decay (and propagate) count // ENTRY/EXIT // fWrite TRUE if want to update object and timestamp, o.w. FALSE // cNew (return) new count // DESCRIPTION // on a read, we do the decay but don't update the object. on the write // we decay and update. // NOTES // todo: if/when we make cCnt a vector, we can propagate stuff here. // this would allow us to usually inc a single small-granularity elt, // and propagate to the large-gran elts only when we really need them. // perf: we could make the table 'cumulative', then we wouldn't have // to do as much computation. not worth the trouble... int CUACount::_DecayCount(BOOL fWrite) { int cCnt; cCnt = _cCnt; if (cCnt > 0 || fWrite) { UINT sidNow; sidNow = _puas->GetSessionId(); if (!_fNoDecay) { // from mso-9 spec // last used 'timTab' sessions ago => dec by >-of abs, pct // n.b. this table is non-cumulative static const int timTab[] = { 3, 6, 9, 12, 17, 23, 29, 31, -1, }; static const int absTab[] = { 1, 1, 1, 2, 3, 4, 5, 0, 0, }; static const int pctTab[] = { 0, 0, 0, 25, 25, 50, 75, 100, 100, }; UINT sidMru; int dt; int i; sidMru = _sidMru; ASSERT(!ISSID_SSPECIAL(_sidMru)); ASSERT(sidMru != SID_SDEFAULT); if (sidMru != SID_SDEFAULT) { dt = sidNow - sidMru; // iterate fwd not bkwd so bail early in common case for (i = 0; i < ARRAYSIZE(timTab); i++) { if ((UINT)dt < (UINT)timTab[i]) break; cCnt -= MAX(absTab[i], PCTOF(cCnt, pctTab[i])); // don't go negative! // gotta check *each* time thru loop (o.w. PCT is bogus) cCnt = MAX(0, cCnt); } } } if (cCnt != _cCnt) TraceMsg(DM_UEMTRACE, "uac.dc: decay %d->%d", _cCnt, cCnt); if (fWrite) { _sidMru = sidNow; _cCnt = cCnt; } #if XXX_DELETE if (cCnt == 0 && !_fInherited) { // if we decay down to 0, mark so it will be deleted TraceMsg(DM_UEMTRACE, "uac.dc: decay %d->%d => mark dirty pRaw=0x%x", _cCnt, cCnt, _GetRawData()); _cCnt = 0; _fDirty = TRUE; } #endif } return cCnt; } //*** // NOTES // perf: currently all special guys return sidNow so no 'switch' necessary UINT CUACount::_ExpandSpecial(UINT sidMru) { UINT sidNow; if (EVAL(ISSID_SSPECIAL(sidMru))) { ASSERT(_puas); sidNow = _puas->GetSessionId(); // perf: multiple calls switch (sidMru) { case SID_SNOWALWAYS: return sidNow; //break; case SID_SNOWREAD: case SID_SNOWINIT: return sidNow; //break; #ifdef DEBUG default: ASSERT(0); break; #endif } } return sidMru; } // Return the encoded filetime. This is read from the registry or // generated from UpdateFileTime. FILETIME CUACount::GetFileTime() { return _ftExecuteTime; } // Updates the internal filetime information. This info // will be later persisted to the registry. void CUACount::UpdateFileTime() { SYSTEMTIME st; // Get the current system time. GetSystemTime(&st); // This is done for ARP. They use filetimes, not the system time // for the calculation of the last execute time. SystemTimeToFileTime(&st, &_ftExecuteTime); } // { //*** UATIME -- //*** FTToUATime -- convert FILETIME to UATIME // DESCRIPTION // UATIME granularity is (approximately) 1 minute. the math works out // roughly as follows: // filetime granularity is 100 nanosec // 1 ft = 10^-7 sec // highword is 2^32 ft = 2^32 * 10^-7 sec // 1 sec = hiw / (2^32 * 10^-7) // 1 min = hiw * 60 / (2^32 * 10^-7) // = hiw * 60 / (1G * 10^-7) // ~= hiw * 60 / ~429 // = hiw / 7.15 // ~= hiw / 8 approx // the exact granularity is: // ... #define FTToUATime(pft) ((DWORD)(*(_int64 *)(pft) >> 29)) // 1 minute (approx) //*** GetUaTime -- convert systemtime (or 'now') to UATIME // UATIME GetUaTime(LPSYSTEMTIME pst) { FILETIME ft; UATIME uat; if (pst == NULL) { GetSystemTimeAsFileTime(&ft); } else { SystemTimeToFileTime(pst, &ft); } uat = FTToUATime(&ft); // minutes return uat; } // }