Audacity 3.2.0
Journal.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Journal.cpp
6
7 Paul Licameli
8
9*******************************************************************//*******************************************************************/
15
16#include "Journal.h"
17#include "JournalOutput.h"
18#include "JournalRegistry.h"
19
20#include <algorithm>
21#include <wx/app.h>
22#include <wx/filename.h>
23#include <wx/ffile.h>
24
25#include <string>
26#include <string_view>
27
28#include "MemoryX.h"
29#include "Prefs.h"
30#include "FileNames.h"
31
32namespace Journal {
33
34namespace {
35
36wxString sFileNameIn;
37wxTextFile sFileIn;
38
39wxString sLine;
40// Invariant: the input file has not been opened, or else sLineNumber counts
41// the number of lines consumed by the tokenizer
42int sLineNumber = -1;
43
44BoolSetting JournalEnabled{ L"/Journal/Enabled", false };
45
46class JournalLogger final
47{
48public:
50 {
51 wxFileName logFile(FileNames::DataDir(), L"journallog.txt");
52 mLogFile.Open(logFile.GetFullPath(wxPATH_NATIVE), L"w");
53 }
54 void WriteString(std::string_view str)
55 {
56 mLogFile.Write(str.data(), str.size());
57 }
58
60 {
61 mLogFile.Write("\n");
62 mLogFile.Flush();
63 }
64
65private:
66 wxFFile mLogFile;
67};
68
70{
71 static JournalLogger logger;
72 return logger;
73}
74
75std::string ToString(const wxString& str)
76{
77 return str.ToStdString();
78}
79
80template<typename T>
81std::string ToString(const T& arg)
82{
83 return std::to_string(arg);
84}
85
86template<typename... Args>
87void Log( std::string_view message, const Args&... args )
88{
89 if (message.empty())
90 return;
91
92 constexpr auto n = sizeof...(Args);
93
94 std::string strings[n];
95 std::size_t i = 0;
96 ((strings[i++] = ToString(args)), ...);
97 i = 0;
98
99 auto& logger = GetLogger();
100
101 while (!message.empty())
102 {
103 const auto placeholderPos = message.find("{}");
104
105 if (placeholderPos == std::string_view::npos || i == n)
106 {
107 logger.WriteString(message);
108 break;
109 }
110
111 std::string_view arg = strings[i++];
112
113 logger.WriteString(message.substr(0, placeholderPos));
114 logger.WriteString(arg);
115
116 message = message.substr(placeholderPos + 2);
117 }
118
119 logger.FinalizeMessge();
120}
121
122inline void NextIn()
123{
124 if ( !sFileIn.Eof() ) {
125 sLine = sFileIn.GetNextLine();
126 ++sLineNumber;
127
128 Log("Journal: line {} is '{}'", sLineNumber, sLine);
129 }
130}
131
133{
134 wxArrayStringEx tokens;
135 if ( Journal::IsReplaying() )
136 for ( ; !sFileIn.Eof(); NextIn() ) {
137 if ( sLine.StartsWith( CommentCharacter ) )
138 continue;
139
140 tokens = wxSplit( sLine, SeparatorCharacter, EscapeCharacter );
141 if ( tokens.empty() )
142 // Ignore blank lines
143 continue;
144
145 break;
146 }
147 return tokens;
148}
149
150constexpr auto VersionToken = wxT("Version");
151
152// Numbers identifying the journal format version
154 1
155};
156
158{
159 wxString result;
160 for ( auto number : journalVersionNumbers ) {
161 auto str = wxString::Format( "%d", number );
162 result += ( result.empty() ? str : ( '.' + str ) );
163 }
164 return result;
165}
166
168bool VersionCheck( const wxString &value )
169{
170 auto strings = wxSplit( value, '.' );
171 std::vector<int> numbers;
172 for ( auto &string : strings ) {
173 long value;
174 if ( !string.ToCLong( &value ) )
175 return false;
176 numbers.push_back( value );
177 }
178 // OK if the static version number is not less than the given value
179 // Maybe in the future there will be a compatibility break
180 return !std::lexicographical_compare(
182 numbers.begin(), numbers.end() );
183}
184
185}
186
187SyncException::SyncException(const wxString& string)
188{
189 // If the exception is ever constructed, cause nonzero program exit code
190 SetError();
191
192 Log("Journal sync failed: {}", string);
193}
194
196
198{
199 // Simulate the application Exit menu item
200 wxCommandEvent evt{ wxEVT_MENU, wxID_EXIT };
201 wxTheApp->AddPendingEvent( evt );
202}
203
205{
206 return JournalEnabled.Read();
207}
208
209bool SetRecordEnabled(bool value)
210{
211 auto result = JournalEnabled.Write(value);
212 gPrefs->Flush();
213 return result;
214}
215
217{
218 return sFileIn.IsOpened();
219}
220
221void SetInputFileName(const wxString &path)
222{
223 sFileNameIn = path;
224}
225
226bool Begin( const FilePath &dataDir )
227{
228 if ( !GetError() && !sFileNameIn.empty() ) {
229 wxFileName fName{ sFileNameIn };
230 fName.MakeAbsolute( dataDir );
231 const auto path = fName.GetFullPath();
232 sFileIn.Open( path );
233 if (!sFileIn.IsOpened())
234 {
235 Log("Journal: failed to open journal file \"{}\"", path);
236 SetError();
237 }
238 else {
239 sLine = sFileIn.GetFirstLine();
240 sLineNumber = 0;
241
242 auto tokens = PeekTokens();
243 NextIn();
244
245 if (!(tokens.size() == 2 && tokens[0] == VersionToken &&
246 VersionCheck(tokens[1])))
247 {
248 Log("Journal: invalid journal version: \"{}\"", tokens[1]);
249 SetError();
250 }
251 }
252 }
253
254 if ( !GetError() && RecordEnabled() ) {
255 wxFileName fName{ dataDir, "journal", "txt" };
256 const auto path = fName.GetFullPath();
257 if ( !OpenOut( path ) )
258 SetError();
259 else {
260 // Generate a header
261 Comment( wxString::Format(
262 wxT("Journal recorded by %s on %s")
263 , wxGetUserName()
264 , wxDateTime::Now().Format()
265 ) );
267 }
268 }
269
270 // Call other registered initialization steps
271 for (auto &initializer : GetInitializers()) {
272 if (initializer && !initializer()) {
273 SetError();
274 break;
275 }
276 }
277
278 return !GetError();
279}
280
282{
283 auto result = PeekTokens();
284 if ( !result.empty() ) {
285 NextIn();
286 return result;
287 }
288 throw SyncException("unexpected end of stream");
289}
290
292{
293 if ( GetError() )
294 // Don't repeatedly indicate error
295 // Do nothing
296 return false;
297
298 if ( !IsReplaying() )
299 return false;
300
301 // This will throw if no lines remain. A proper journal should exit the
302 // program before that happens.
303 auto words = GetTokens();
304
305 // Lookup dispatch function by the first field of the line
306 auto &table = GetDictionary();
307 auto &name = words[0];
308 auto iter = table.find( name );
309 if (iter == table.end())
310 throw SyncException(
311 wxString::Format("unknown command: %s", name.ToStdString().c_str()));
312
313 // Pass all the fields including the command name to the function
314 if (!iter->second(words))
315 throw SyncException(wxString::Format(
316 "command '%s' has failed", wxJoin(words, ',').ToStdString().c_str()));
317
318 return true;
319}
320
321void Sync( const wxString &string )
322{
323 if ( IsRecording() || IsReplaying() ) {
324 if ( IsRecording() )
325 Output( string );
326 if ( IsReplaying() ) {
327 if (sFileIn.Eof() || sLine != string)
328 {
329 throw SyncException(wxString::Format(
330 "sync failed. Expected '%s', got '%s'",
331 string.ToStdString().c_str(), sLine.ToStdString().c_str()));
332 }
333
334 NextIn();
335 }
336 }
337}
338
339void Sync( const wxArrayString &strings )
340{
341 if ( IsRecording() || IsReplaying() ) {
342 auto string = ::wxJoin( strings, SeparatorCharacter, EscapeCharacter );
343 Sync( string );
344 }
345}
346
347void Sync( std::initializer_list< const wxString > strings )
348{
349 return Sync( wxArrayStringEx( strings ) );
350}
351
353 const wxString &string, const InteractiveAction &action )
354{
355 // Special journal word
356 Sync(string);
357
358 // Then read or write the return value on another journal line
359 if ( IsReplaying() ) {
360 auto tokens = GetTokens();
361 if ( tokens.size() == 1 ) {
362 try {
363 std::wstring str{ tokens[0].wc_str() };
364 size_t length = 0;
365 auto result = std::stoi(str, &length);
366 if (length == str.length()) {
367 if (IsRecording())
368 Journal::Output( std::to_wstring(result) );
369 return result;
370 }
371 }
372 catch ( const std::exception& ) {}
373 }
374
375 throw SyncException(wxString::Format(
376 "unexpected tokens: %s", wxJoin(tokens, ',').ToStdString().c_str()));
377 }
378 else {
379 auto result = action ? action() : 0;
380 if ( IsRecording() )
381 Output( std::to_wstring( result ) );
382 return result;
383 }
384}
385
387{
388 // Unconsumed commands remaining in the input file is also an error condition.
389 if( !GetError() && !PeekTokens().empty() ) {
390 NextIn();
391 SetError();
392 }
393 if ( GetError() ) {
394 // Return nonzero
395 // Returning the (1-based) line number at which the script failed is a
396 // simple way to communicate that information to the test driver script.
397 return sLineNumber ? sLineNumber : -1;
398 }
399
400 // Return zero to mean all is well, the convention for command-line tools
401 return 0;
402}
403
404}
wxT("CloseDown"))
#define str(a)
const TranslatableString name
Definition: Distortion.cpp:76
The output stream of the journal system.
Journal system's error status, command dictionary, initializers.
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
wxString FilePath
Definition: Project.h:21
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:346
Abstract base class used in importing a file.
SyncException(const wxString &message)
Constructs an exception with a message; message is logged into the journallog.txt file.
Definition: Journal.cpp:187
~SyncException() override
Definition: Journal.cpp:195
void DelayedHandlerAction() override
Action to do in the main thread at idle time of the event loop.
Definition: Journal.cpp:197
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:259
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
virtual bool Flush() noexcept=0
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
FILES_API FilePath DataDir()
Audacity user data directory.
std::string ToString(const T &arg)
Definition: Journal.cpp:81
void Log(std::string_view message, const Args &... args)
Definition: Journal.cpp:87
bool VersionCheck(const wxString &value)
True if value is an acceptable journal version number to be rerun.
Definition: Journal.cpp:168
Facilities for recording and playback of sequences of user interaction.
int GetExitCode()
Definition: Journal.cpp:386
bool Begin(const FilePath &dataDir)
Definition: Journal.cpp:226
void SetInputFileName(const wxString &path)
Definition: Journal.cpp:221
void Sync(const wxString &string)
Definition: Journal.cpp:321
const Dictionary & GetDictionary()
bool SetRecordEnabled(bool value)
Definition: Journal.cpp:209
bool GetError()
void Comment(const wxString &string)
const Initializers & GetInitializers()
constexpr auto EscapeCharacter
Definition: JournalOutput.h:23
wxArrayStringEx GetTokens()
Definition: Journal.cpp:281
bool RecordEnabled()
Definition: Journal.cpp:204
int IfNotPlaying(const wxString &string, const InteractiveAction &action)
Call action only if not replaying; synchronize on string and int values.
Definition: Journal.cpp:352
void Output(const wxString &string)
bool IsReplaying()
Definition: Journal.cpp:216
constexpr auto SeparatorCharacter
Definition: JournalOutput.h:22
bool Dispatch()
Definition: Journal.cpp:291
std::function< int() > InteractiveAction
Function that returns a value which will be written to the journal.
Definition: Journal.h:79
constexpr auto CommentCharacter
Definition: JournalOutput.h:24
void SetError()
bool OpenOut(const wxString &fullPath)
bool IsRecording()
constexpr size_t npos(-1)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101