convert line ends
[canaan.git] / prj / tech / libsrc / recorder / recorder.cpp
bloba7b84925aa2b1536faad859636d652ce89744a0c
1 ///////////////////////////////////////////////////////////////////////////////
2 // $Source: x:/prj/tech/libsrc/recorder/RCS/recorder.cpp $
3 // $Author: JAEMZ $
4 // $Date: 1997/09/28 20:27:21 $
5 // $Revision: 1.20 $
6 //
8 #include <comtools.h>
9 #include <lg.h>
10 #include <appapi.h>
11 #include <appagg.h>
12 #include <aggmemb.h>
14 #include <recorder.h>
15 #include <recprmpt.h>
17 #include <filepath.h>
18 #include <fcntl.h>
19 #include <io.h>
20 #include <sys\stat.h>
21 #include <sys\types.h>
22 #include <time.h> // time, localtime
23 #include <string.h> // strncat
24 #include <stdlib.h> // getenv
25 #include <str.h>
27 ///////////////////////////////////////////////////////////////////////////////
29 #ifdef _WIN32
30 extern "C"
32 __declspec(dllimport) DWORD __stdcall GetCurrentThreadId(void);
34 #define AssertCorrectThread() AssertMsg(GetCurrentThreadId() == m_ThreadRecordingId, "Cannot record actions in multiple threads.");
35 #else
36 #define AssertCorrectThread()
37 #endif
39 const unsigned kRecVersion = 3;
41 ///////////////////////////////////////////////////////////////////////////////
43 tResult LGAPI
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)
50 return S_OK;
51 return E_FAIL;
54 ///////////////////////////////////////////////////////////////////////////////
56 // CLASS: cRecorder
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)
72 : m_mode(mode),
73 m_RecFile(-1),
74 m_fSaveOption(fSaveOption),
75 m_fFirstAccess(TRUE)
76 #ifdef _WIN32
77 , m_ThreadRecordingId(GetCurrentThreadId())
78 #endif
80 // Add internal components to outer aggregate...
81 INIT_AGGREGATION_1( pOuterUnknown,
82 IID_IRecorder, this,
83 (kPriorityLibrary - 1),
84 NULL );
86 // Set the file name...
87 if (pszRecFileName && *pszRecFileName)
88 m_RecFileSpec.SetRelativePath(pszRecFileName);
89 else
91 // Build an unique string based upon the user's login name, current
92 // date and time.
93 char szGeneratedFileName[13];
94 const char * pszUserName = getenv("USER");
95 time_t lTime = time(NULL);
96 tm * pTm = localtime(&lTime);
98 if (pszUserName)
100 strncpy(szGeneratedFileName, pszUserName, 3);
101 szGeneratedFileName[3] = 0;
103 else
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();
125 else
127 if (m_mode == kRecPlayback)
128 m_mode = kRecInactive;
131 if (m_mode == kRecInactive)
132 return;
134 int access = _O_BINARY;
136 if (m_mode == kRecPlayback)
137 access |= _O_RDONLY;
138 else
139 access |= _O_RDWR | _O_CREAT | _O_TRUNC;
141 m_RecFile = open(m_RecFileSpec.GetName(), access, S_IREAD | S_IWRITE);
143 if (m_RecFile == -1)
145 CriticalMsg1("Failed to open recording file %s", m_RecFileSpec.GetName());
146 m_mode = kRecInactive;
147 return;
150 m_pszLastStreamItem = "(no previous)";
151 m_sizeLastItem = 0;
155 ///////////////////////////////////////
157 cRecorder::~cRecorder()
159 // Delete the hash table entries
161 // Close the recording file
162 if (m_RecFile != -1)
163 close(m_RecFile);
165 switch (m_fSaveOption)
167 case kRecSavePrompted:
168 if (RecPromptYesNo("Save recording file?"))
169 break;
170 // else fall through
172 case kRecDiscardIfNoFailure:
173 m_RecFileSpec.UnlinkFile();
177 ///////////////////////////////////////
179 const uint8 kRecTypeDesc = 1;
180 const uint8 kRecTypedData = 2;
181 const uint8 kRecRawData = 3;
183 #pragma pack(1)
184 struct sRecBlockHeader
186 tRecBlockType blockType;
188 union
190 // Type descriptor
191 uint8 typeSize;
193 // Typed data
194 uint8 typeID;
196 // Raw data
197 uint8 rawSize;
200 #pragma pack()
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()
232 return m_mode;
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)
241 switch (m_mode)
243 case kRecRecord:
245 return cRecorder::AddToStream(pData, sizeData, pszTypeTag);
247 case kRecPlayback:
249 return cRecorder::ExtractFromStream(pData, sizeData, pszTypeTag);
252 return S_OK;
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...
266 if (m_fFirstAccess)
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();
275 else
276 pszProject = "Unknown";
277 const char * pszUser = (getenv("USER")) ? getenv("USER") : "Unknown";
278 time_t lTime = time(NULL);
279 tm * pTm = localtime(&lTime);
281 cStr recHeaderStr;
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...
289 if (pszTypeTag)
291 // Determine if this is a new type name, if not, add it
292 sRecDataType *pDataType = m_RecDataTypeTable.Search(pszTypeTag);
294 if (!pDataType)
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;
314 return E_FAIL;
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;
323 return E_FAIL;
326 // Write the data, typed
327 if (!WriteBlock(kRecTypedData, pDataType->typeID, pData, sizeData))
329 CriticalMsg("Error writing to recording");
330 m_mode = kRecInactive;
331 return E_FAIL;
334 else
336 // Write the data, raw
337 if (!WriteBlock(kRecRawData, (uint8)sizeData, pData, sizeData))
339 CriticalMsg("Error writing to recording");
340 m_mode = kRecInactive;
341 return E_FAIL;
345 return S_OK;
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");
358 if (m_fFirstAccess)
360 char c;
361 int version;
363 // Skip header text
366 read(m_RecFile, &c, 1);
367 } while (c);
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;
374 return S_FALSE;
376 m_fFirstAccess = FALSE;
379 ///////////////////////////////////
381 sRecBlockHeader header;
383 enum eRecReadState
385 kReading,
386 kDoneSuccess,
387 kDoneInconsistentType,
388 kDoneExpectedTag,
389 kDoneBadRead,
392 const char * ppszReadErrors[] =
394 "Still reading",
395 "No error",
396 "Inconsistent type",
397 "Expected typed data",
398 "Bad read",
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)
412 case kRecTypeDesc:
414 if (pszTypeTag)
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
430 else
431 readState = kDoneExpectedTag;
432 break;
435 case kRecTypedData:
437 if (pszTypeTag)
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;
448 else
449 readState = kDoneBadRead;
451 else
452 readState = kDoneInconsistentType;
454 else
455 readState = kDoneExpectedTag;
456 break;
459 case kRecRawData:
461 pszThisStreamItem = "raw data";
462 sizeThisItem = header.rawSize;
464 if (header.rawSize == sizeData)
466 if (ReadBlockData(pData, sizeData))
467 readState = kDoneSuccess;
468 else
469 readState = kDoneBadRead;
471 else
472 readState = kDoneInconsistentType;
473 break;
476 default:
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"
488 "Got %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",
493 sizeData,
494 pszThisStreamItem,
495 sizeThisItem,
496 m_pszLastStreamItem,
497 m_sizeLastItem);
498 m_mode = kRecInactive;
499 return (fEndOfRecording) ? S_FALSE : E_FAIL;
502 m_pszLastStreamItem = pszThisStreamItem;
503 m_sizeLastItem = sizeThisItem;
505 return S_OK;
508 ///////////////////////////////////////
510 STDMETHODIMP cRecorder::Pause()
512 int recorderPauseMarker = 0;
514 switch (m_mode)
516 case kRecRecord:
518 m_mode = kRecPausedRecord;
519 break;
521 case kRecPlayback:
523 m_mode = kRecPausedPlayback;
524 break;
527 StreamAddOrExtract(&recorderPauseMarker, sizeof(recorderPauseMarker), "Record pause marker");
528 return S_OK;
531 ///////////////////////////////////////
533 STDMETHODIMP cRecorder::Resume()
535 int recorderResumeMarker = 0;
537 switch (m_mode)
539 case kRecPausedRecord:
541 m_mode = kRecRecord;
542 break;
544 case kRecPausedPlayback:
546 m_mode = kRecPlayback;
547 break;
549 default:
550 CriticalMsg("Called recorder resume when not paused");
552 StreamAddOrExtract(&recorderResumeMarker, sizeof(recorderResumeMarker), "Record resume marker");
553 return S_OK;
556 ///////////////////////////////////////////////////////////////////////////////
558 tHashSetKey cRecDataTypeTable::GetKey(tHashSetNode node) const
560 return (tHashSetKey) (((sRecDataType *) (node))->typeName);
563 ///////////////////////////////////////////////////////////////////////////////