1 ///////////////////////////////////////////////////////////////////////////////
2 // $Source: x:/prj/tech/libsrc/recorder/RCS/recorder.cpp $
4 // $Date: 1997/09/28 20:27:21 $
21 #include <sys\types.h>
22 #include <time.h> // time, localtime
23 #include <string.h> // strncat
24 #include <stdlib.h> // getenv
27 ///////////////////////////////////////////////////////////////////////////////
32 __declspec(dllimport
) DWORD __stdcall
GetCurrentThreadId(void);
34 #define AssertCorrectThread() AssertMsg(GetCurrentThreadId() == m_ThreadRecordingId, "Cannot record actions in multiple threads.");
36 #define AssertCorrectThread()
39 const unsigned kRecVersion
= 3;
41 ///////////////////////////////////////////////////////////////////////////////
44 _RecorderCreate(REFIID
, IRecorder
** /* ppRecorder */ ,
45 IUnknown
* pOuterUnknown
, eRecMode mode
,
46 const char * pszRecFilepath
, const char * pszRecFileName
,
47 enum eRecSaveOption fSaveOption
)
49 if (new cRecorder(pOuterUnknown
, mode
, pszRecFilepath
, pszRecFileName
, fSaveOption
) != 0)
54 ///////////////////////////////////////////////////////////////////////////////
58 // Implementation of generic record/playback system
61 ///////////////////////////////////////
63 // Pre-fab COM implementations
65 IMPLEMENT_SIMPLE_AGGREGATION_SELF_DELETE(cRecorder
);
67 ///////////////////////////////////////
69 cRecorder::cRecorder(IUnknown
* pOuterUnknown
, eRecMode mode
,
70 const char * pszRecFilepath
, const char *pszRecFileName
,
71 eRecSaveOption fSaveOption
)
74 m_fSaveOption(fSaveOption
),
77 , m_ThreadRecordingId(GetCurrentThreadId())
80 // Add internal components to outer aggregate...
81 INIT_AGGREGATION_1( pOuterUnknown
,
83 (kPriorityLibrary
- 1),
86 // Set the file name...
87 if (pszRecFileName
&& *pszRecFileName
)
88 m_RecFileSpec
.SetRelativePath(pszRecFileName
);
91 // Build an unique string based upon the user's login name, current
93 char szGeneratedFileName
[13];
94 const char * pszUserName
= getenv("USER");
95 time_t lTime
= time(NULL
);
96 tm
* pTm
= localtime(&lTime
);
100 strncpy(szGeneratedFileName
, pszUserName
, 3);
101 szGeneratedFileName
[3] = 0;
104 strcpy(szGeneratedFileName
, "REC");
106 itoa((pTm
->tm_yday
<< 11) | (pTm
->tm_min
+ (pTm
->tm_hour
* 60)), szGeneratedFileName
+ 3, 16);
107 strcat(szGeneratedFileName
, ".rec");
108 m_RecFileSpec
.SetRelativePath(szGeneratedFileName
);
111 // Complete the file path...
112 if (pszRecFilepath
&& *pszRecFilepath
)
114 m_RecFileSpec
.SetFilePath(pszRecFilepath
);
117 m_RecFileSpec
.MakeFullPath();
119 // Open the recording file...
120 if (m_RecFileSpec
.FileExists())
122 if (m_mode
== kRecRecord
)
123 m_RecFileSpec
.UnlinkFile();
127 if (m_mode
== kRecPlayback
)
128 m_mode
= kRecInactive
;
131 if (m_mode
== kRecInactive
)
134 int access
= _O_BINARY
;
136 if (m_mode
== kRecPlayback
)
139 access
|= _O_RDWR
| _O_CREAT
| _O_TRUNC
;
141 m_RecFile
= open(m_RecFileSpec
.GetName(), access
, S_IREAD
| S_IWRITE
);
145 CriticalMsg1("Failed to open recording file %s", m_RecFileSpec
.GetName());
146 m_mode
= kRecInactive
;
150 m_pszLastStreamItem
= "(no previous)";
155 ///////////////////////////////////////
157 cRecorder::~cRecorder()
159 // Delete the hash table entries
161 // Close the recording file
165 switch (m_fSaveOption
)
167 case kRecSavePrompted
:
168 if (RecPromptYesNo("Save recording file?"))
172 case kRecDiscardIfNoFailure
:
173 m_RecFileSpec
.UnlinkFile();
177 ///////////////////////////////////////
179 const uint8 kRecTypeDesc
= 1;
180 const uint8 kRecTypedData
= 2;
181 const uint8 kRecRawData
= 3;
184 struct sRecBlockHeader
186 tRecBlockType blockType
;
202 ///////////////////////////////////////
204 inline BOOL
cRecorder::WriteBlock(tRecBlockType type
, uint8 dataInfo
, const void * pData
, size_t dataSize
)
206 write(m_RecFile
, &type
, sizeof(type
));
207 write(m_RecFile
, &dataInfo
, sizeof(dataInfo
));
208 return write(m_RecFile
, pData
, dataSize
) == dataSize
;
211 ///////////////////////////////////////
213 inline BOOL
cRecorder::ReadBlockHeader(sRecBlockHeader
* pHeader
)
215 return read(m_RecFile
, pHeader
, sizeof(sRecBlockHeader
)) == sizeof(sRecBlockHeader
);
218 ///////////////////////////////////////
220 inline BOOL
cRecorder::ReadBlockData(void * pReadTo
, size_t size
)
222 return read(m_RecFile
, pReadTo
, size
) == size
;
225 ///////////////////////////////////////
227 // Query the present recorder mode
230 STDMETHODIMP_(eRecMode
) cRecorder::GetMode()
235 ///////////////////////////////////////
237 STDMETHODIMP
cRecorder::StreamAddOrExtract(void * pData
, size_t sizeData
, const char * pszTypeTag
)
239 // We call directly to cRecorder here to save the cost of the virtual function, remove
240 // if these are overridden at a later date (toml 09-30-96)
245 return cRecorder::AddToStream(pData
, sizeData
, pszTypeTag
);
249 return cRecorder::ExtractFromStream(pData
, sizeData
, pszTypeTag
);
255 ///////////////////////////////////////
257 // Add arbitrary data to recording stream
260 STDMETHODIMP
cRecorder::AddToStream(void *pData
, size_t sizeData
, const char *pszTypeTag
)
262 AssertCorrectThread();
263 AssertMsg(m_mode
!= kRecPlayback
, "Can't AddToStream() when playing back");
265 // If this is the first use...
268 // ... write the recording header
269 AutoAppIPtr(Application
);
270 const char pcszRecHeader
[] = "---\r\nProject: %s\r\nUser: %s\r\nDate/Time: %s\r\nLGT Recording System Version %d\r\n---\r\n\x1a";
272 const char * pszProject
;
273 if ((void *)pApplication
&& pApplication
->GetCaption())
274 pszProject
= pApplication
->GetCaption();
276 pszProject
= "Unknown";
277 const char * pszUser
= (getenv("USER")) ? getenv("USER") : "Unknown";
278 time_t lTime
= time(NULL
);
279 tm
* pTm
= localtime(&lTime
);
282 recHeaderStr
.FmtStr(pcszRecHeader
, pszProject
, pszUser
, asctime(pTm
), kRecVersion
);
283 write(m_RecFile
, recHeaderStr
.operator const char *(), recHeaderStr
.GetLength() + 1);
284 write(m_RecFile
, &kRecVersion
, sizeof(kRecVersion
));
285 m_fFirstAccess
= FALSE
;
288 // If the stream addition is typed...
291 // Determine if this is a new type name, if not, add it
292 sRecDataType
*pDataType
= m_RecDataTypeTable
.Search(pszTypeTag
);
296 AssertMsg1(strlen(pszTypeTag
) <= kMaxTypeNameLen
, "Type name \"%s\" too long for recorder", pszTypeTag
);
297 AssertMsg1(sizeData
<= kRecMaxDataSize
, "Type \"%s\" too big for recorder -- record it in smaller pieces", pszTypeTag
);
298 AssertMsg1(m_RecDataTypeTable
.GetCount() < kMaxTypes
, "Type \"%s\" exceeds total maximum of recordable types", pszTypeTag
);
300 pDataType
= NewRecDataType();
302 pDataType
->typeSize
= (uint8
)sizeData
;
303 strncpy(pDataType
->typeName
, pszTypeTag
, kMaxTypeNameLen
);
304 pDataType
->typeName
[kMaxTypeNameLen
] = '\0';
306 // Add the new type to the hash table.
307 m_RecDataTypeTable
.Insert(pDataType
);
309 // Write out the new type block
310 if (!WriteBlock(kRecTypeDesc
, pDataType
->typeSize
, pDataType
->typeName
, kMaxTypeNameLen
+ 1))
312 CriticalMsg("Error writing to recording");
313 m_mode
= kRecInactive
;
318 // Verify type size now is same as previous add
319 if (pDataType
->typeSize
!= sizeData
)
321 CriticalMsg("Invalid size for recording data");
322 m_mode
= kRecInactive
;
326 // Write the data, typed
327 if (!WriteBlock(kRecTypedData
, pDataType
->typeID
, pData
, sizeData
))
329 CriticalMsg("Error writing to recording");
330 m_mode
= kRecInactive
;
336 // Write the data, raw
337 if (!WriteBlock(kRecRawData
, (uint8
)sizeData
, pData
, sizeData
))
339 CriticalMsg("Error writing to recording");
340 m_mode
= kRecInactive
;
348 ///////////////////////////////////////
350 // Extract arbitrary data from recording stream
353 STDMETHODIMP
cRecorder::ExtractFromStream(void *pData
, size_t sizeData
, const char *pszTypeTag
)
355 AssertCorrectThread();
356 AssertMsg(m_mode
!= kRecRecord
, "Can't ExtractFromStream() when recording");
366 read(m_RecFile
, &c
, 1);
369 read(m_RecFile
, &version
, sizeof(version
));
370 if (version
!= kRecVersion
)
372 CriticalMsg2("Cannot use recording -- incompatible recorder version\nRecording is from version %d, current is %d", version
, kRecVersion
);
373 m_mode
= kRecInactive
;
376 m_fFirstAccess
= FALSE
;
379 ///////////////////////////////////
381 sRecBlockHeader header
;
387 kDoneInconsistentType
,
392 const char * ppszReadErrors
[] =
397 "Expected typed data",
401 const char * pszThisStreamItem
= "<unknown>";
402 size_t sizeThisItem
= 0;
404 eRecReadState readState
= kReading
;
405 BOOL fEndOfRecording
= TRUE
;
407 while (readState
== kReading
&& ReadBlockHeader(&header
))
409 fEndOfRecording
= FALSE
;
410 switch (header
.blockType
)
416 sRecDataType
* pNewDataType
= NewRecDataType();
417 pNewDataType
->typeSize
= header
.typeSize
;
418 ReadBlockData(pNewDataType
->typeName
, kMaxTypeNameLen
+ 1);
420 m_RecDataTypeTable
.Insert(pNewDataType
);
422 pszThisStreamItem
= "new data type";
423 sizeThisItem
= header
.typeSize
;
425 if (strcmp(pNewDataType
->typeName
, pszTypeTag
) != 0 || pNewDataType
->typeSize
!= sizeData
) // New type should match client value
426 readState
= kDoneInconsistentType
;
428 // else state continues to be kReading
431 readState
= kDoneExpectedTag
;
439 sRecDataType
* pDataType
= m_RecDataTypeTable
.Search(pszTypeTag
);
441 pszThisStreamItem
= m_RecDataTypesArray
[header
.typeID
].typeName
;
442 sizeThisItem
= m_RecDataTypesArray
[header
.typeID
].typeSize
;
444 if (pDataType
&& pDataType
->typeID
== header
.typeID
&& sizeThisItem
== sizeData
)
446 if (ReadBlockData(pData
, sizeData
))
447 readState
= kDoneSuccess
;
449 readState
= kDoneBadRead
;
452 readState
= kDoneInconsistentType
;
455 readState
= kDoneExpectedTag
;
461 pszThisStreamItem
= "raw data";
462 sizeThisItem
= header
.rawSize
;
464 if (header
.rawSize
== sizeData
)
466 if (ReadBlockData(pData
, sizeData
))
467 readState
= kDoneSuccess
;
469 readState
= kDoneBadRead
;
472 readState
= kDoneInconsistentType
;
477 CriticalMsg("Error in playback stream: unknown recording file block type");
478 readState
= kDoneBadRead
;
482 ///////////////////////////////////
484 if (readState
!= kDoneSuccess
)
486 AssertMsg7(fEndOfRecording
, "Playback error \"%s\".\n"
487 "Expected %s sized %u,\n"
489 "Last was %s sized %u.\n"
490 "Recording is inconsistent and/or invalid.",
491 ppszReadErrors
[readState
],
492 (pszTypeTag
) ? pszTypeTag
: "raw data",
498 m_mode
= kRecInactive
;
499 return (fEndOfRecording
) ? S_FALSE
: E_FAIL
;
502 m_pszLastStreamItem
= pszThisStreamItem
;
503 m_sizeLastItem
= sizeThisItem
;
508 ///////////////////////////////////////
510 STDMETHODIMP
cRecorder::Pause()
512 int recorderPauseMarker
= 0;
518 m_mode
= kRecPausedRecord
;
523 m_mode
= kRecPausedPlayback
;
527 StreamAddOrExtract(&recorderPauseMarker
, sizeof(recorderPauseMarker
), "Record pause marker");
531 ///////////////////////////////////////
533 STDMETHODIMP
cRecorder::Resume()
535 int recorderResumeMarker
= 0;
539 case kRecPausedRecord
:
544 case kRecPausedPlayback
:
546 m_mode
= kRecPlayback
;
550 CriticalMsg("Called recorder resume when not paused");
552 StreamAddOrExtract(&recorderResumeMarker
, sizeof(recorderResumeMarker
), "Record resume marker");
556 ///////////////////////////////////////////////////////////////////////////////
558 tHashSetKey
cRecDataTypeTable::GetKey(tHashSetNode node
) const
560 return (tHashSetKey
) (((sRecDataType
*) (node
))->typeName
);
563 ///////////////////////////////////////////////////////////////////////////////