88
99#include < TkUtil/Date.h>
1010#include < TkUtil/File.h>
11+ #include < TkUtil/FileLock.h>
1112#include < TkUtil/Threads.h>
1213#include < TkUtil/UserData.h>
1314
1415#include < map>
16+ #include < sstream>
1517
1618#include < assert.h>
1719#include < errno.h>
2022#include < string.h> // strerror
2123#include < sys/stat.h> // fchmod
2224#include < sys/types.h>
25+ #include < sys/param.h> // MAXPATHLEN
2326#include < unistd.h> // fork, setsid
2427
2528USING_STD
2629
2730BEGIN_NAMESPACE_UTIL
2831
32+ static const Charset charset (" àéèùô" );
33+
2934
3035ApplicationStats::ApplicationStats ( )
3136{
@@ -195,7 +200,7 @@ void ApplicationStats::logUsage (const string& appName, const string& logDir)
195200 bool found = false ;
196201 int flag = 0 ;
197202 errno = 0 ;
198- while (2 == (flag = fscanf (file, " %s\t %u " , name, &count)))
203+ while (2 == (flag = fscanf (file, " %s\t %lu " , name, &count)))
199204 {
200205 line++;
201206 if (name == user)
@@ -205,7 +210,7 @@ void ApplicationStats::logUsage (const string& appName, const string& logDir)
205210 } // if (name == user)
206211 logs.insert (pair<string, size_t > (name, count));
207212 count = 0 ;
208- } // while (2 == fscanf (file, "%s\t%u ", name, &count))
213+ } // while (2 == fscanf (file, "%s\t%lu ", name, &count))
209214 if (0 != errno)
210215 {
211216 {
@@ -241,15 +246,15 @@ void ApplicationStats::logUsage (const string& appName, const string& logDir)
241246
242247 for (map<string, size_t >::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++)
243248 {
244- if (fprintf (file, " %s\t %u \n " , (*itl).first .c_str ( ), (*itl).second ) < 0 )
249+ if (fprintf (file, " %s\t %lu \n " , (*itl).first .c_str ( ), (*itl).second ) < 0 )
245250 {
246251 {
247252 TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
248253 ConsoleOutput::cerr ( ) << " Erreur lors de la réécriture du fichier de logs " << fileName << " ." << co_endl;
249254 }
250255 fclose (file);
251256 exit (0 );
252- }
257+ } // if (fprintf (file, "%s\t%lu\n", (*itl).first.c_str ( ), (*itl).second) < 0)
253258 } // for (map<string, size_t>::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++)
254259 errno = 0 ;
255260 if (0 != fflush (file))
@@ -281,6 +286,177 @@ void ApplicationStats::logUsage (const string& appName, const string& logDir)
281286} // ApplicationStats::logUsage
282287
283288
289+ /* *
290+ * Structure portant les informations à enregistrer dans un fichier, à savoir la version de l'application, le système d'exploitation, et le chemin
291+ * d'accès complet à l'application.
292+ */
293+ struct OriginInfos
294+ {
295+ OriginInfos (const string& v, const string& o, const string& p)
296+ : version (v), os (o), path (p), key ( )
297+ {
298+ ostringstream s;
299+ s << version << os << path;
300+ key = s.str ( );
301+ }
302+ OriginInfos (const OriginInfos& oi)
303+ : version (oi.version), os (oi.os), path (oi.path), key (oi.key)
304+ { }
305+ OriginInfos& operator = (const OriginInfos& oi)
306+ {
307+ if (&oi != this )
308+ {
309+ version = oi.version ;
310+ os = oi.os ;
311+ path = oi.path ;
312+ key = oi.key ;
313+ }
314+
315+ return *this ;
316+ }
317+ string version, os, path;
318+ string key;
319+ }; // struct OriginInfos
320+
321+
322+ static bool operator == (const OriginInfos& left, const OriginInfos& right)
323+ {
324+ return (left.version == right.version ) && (left.os == right.os ) && (left.path == right.path ) ? true : false ;
325+ } // operator == (const OriginInfos& left, const OriginInfos& right)
326+
327+
328+ static bool operator != (const OriginInfos& left, const OriginInfos& right)
329+ {
330+ return !(left == right);
331+ } // operator != (const OriginInfos& left, const OriginInfos& right)
332+
333+
334+ static bool operator < (const OriginInfos& left, const OriginInfos& right)
335+ {
336+ return left.key < right.key ;
337+ } // operator < (const OriginInfos& left, const OriginInfos& right)
338+
339+
340+
341+ void ApplicationStats::logUsage (const string& appName, const string& logDir, const string& version, const string& os, const string& path)
342+ {
343+ FILE* file = 0 ;
344+ string fileName;
345+
346+ try
347+ {
348+ if ((true == appName.empty ( )) || (true == logDir.empty ( )))
349+ {
350+ {
351+
352+ ConsoleOutput::cerr ( ) << " ApplicationStats::logUsage : nom d'application ou répertoire des logs non renseigné (" << appName << " /" << logDir << " )." << co_endl;
353+ }
354+
355+ return ; // Tolérance aux erreurs
356+ } // if ((true == appName.empty ( )) || (true == logDir.empty ( )))
357+
358+ // Le nom du fichier :
359+ const Date date;
360+ const string user (UserData (true ).getName ( ));
361+ fileName = getFileName (appName + string (" _usages" ), logDir, (unsigned long )date.getMonth ( ), date.getYear ( ));
362+
363+ file = initLogSession (fileName);
364+ if (0 == file)
365+ return ; // Process père
366+
367+ { // FileLock scope
368+ FileLock logLock (fileName, file);
369+
370+ // Lecture et actualisation des logs existants :
371+ map<OriginInfos, size_t > logs;
372+ char v [255 ], o [255 ], p [MAXPATHLEN + 1 ];
373+ size_t count = 0 , line = 1 ;
374+ bool found = false ;
375+ int flag = 0 ;
376+
377+ errno = 0 ;
378+ const OriginInfos addedOi (version, os, path);
379+ while (4 == (flag = fscanf (file, " %s\t %s\t %s\t %lu" , v, o, p, &count)))
380+ {
381+ line++;
382+ const OriginInfos oi (v, o, p);
383+ if (addedOi == oi)
384+ {
385+ found = true ;
386+ count++;
387+ } // if (addedOi == oi)
388+ logs.insert (pair<OriginInfos, size_t >(oi, count));
389+ count = 0 ;
390+ } // while (4 == (flag = fscanf (file, "%s\t%s\t%s\t%lu", v, o, p, &count)))
391+
392+ if (0 != errno)
393+ {
394+ UTF8String error (charset);
395+ error << " Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long )line << strerror (errno);
396+ errno = 0 ;
397+ throw Exception (error);
398+ } // if (0 != errno)
399+ else if ((flag < 4 ) && (EOF != flag))
400+ {
401+ UTF8String error (charset);
402+ error << " Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long )line << " : fichier probablement corrompu." ;
403+ throw Exception (error);
404+ } // if ((flag < 4) && (EOF != flag))
405+
406+ if (false == found)
407+ logs.insert (pair<OriginInfos, size_t >(addedOi, 1 ));
408+
409+ // Réécriture des logs actualisés :
410+ errno = 0 ;
411+ if (0 != fseek (file, 0 , SEEK_SET))
412+ {
413+ UTF8String error (charset);
414+ error << " Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno);
415+ errno = 0 ;
416+ throw Exception (error);
417+ } // if (0 != fseek (file, 0, SEEK_SET))
418+
419+ for (map<OriginInfos, size_t >::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++)
420+ {
421+ if (fprintf (file, " %s\t %s\t %s\t %lu\n " , (*itl).first .version .c_str ( ), (*itl).first .os .c_str ( ),(*itl).first .path .c_str ( ), (*itl).second ) < 0 )
422+ {
423+ UTF8String error (charset);
424+ error << " Erreur lors de la réécriture du fichier de logs " << fileName << " ." ;
425+ throw Exception (error);
426+ } // if (fprintf (file, "%s\t%s\t%s\t%lu\n", (*itl).first.version.c_str ( ), (*itl).first.os.c_str ( ),(*itl).first.path.c_str ( ), (*itl).second) < 0)
427+ } // for (map<OriginInfos, size_t>::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++)
428+
429+ errno = 0 ;
430+ if (0 != fflush (file))
431+ {
432+ UTF8String error (charset);
433+ error << " Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno);
434+ errno = 0 ;
435+ throw Exception (error);
436+ } // if (0 != fflush (file))
437+ } // FileLock scope
438+ }
439+ catch (const Exception& exc)
440+ {
441+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
442+ ConsoleOutput::cerr ( ) << exc.getFullMessage ( ) << co_endl;
443+ }
444+ catch (...)
445+ {
446+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
447+ ConsoleOutput::cerr ( ) << " ApplicationStats::logUsage : erreur non renseignée lors de l'écriture d'informations dans le fichier \" " << fileName << " \" ." << co_endl;
448+ }
449+
450+ if (0 != file)
451+ {
452+ fclose (file);
453+ file = 0 ;
454+ } // if (0 != file)
455+
456+ exit (0 );
457+ } // ApplicationStats::logUsage
458+
459+
284460void ApplicationStats::logStats (std::ostream& output, const std::string& appName, const string& from, const string& to, const std::string& logDir)
285461{
286462 map<string, size_t > logs;
@@ -335,6 +511,111 @@ void ApplicationStats::logStats (std::ostream& output, const std::string& appNam
335511} // ApplicationStats::logStats
336512
337513
514+ FILE* ApplicationStats::initLogSession (const string fullFileName)
515+ {
516+ // En vue de ne pas altérer le comportement de l'application tout est effectuée dans un processus fils => exit (0) en toutes circonstances.
517+ errno = 0 ;
518+ const pid_t pid = fork ( );
519+ if ((pid_t )-1 == pid)
520+ {
521+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
522+ ConsoleOutput::cerr ( ) << " ApplicationStats::initLogSession : échec de fork : " << strerror (errno) << co_endl;
523+ return 0 ;
524+ } // if ((pid_t)-1 == pid)
525+ if (0 != pid)
526+ {
527+ Process::killAtEnd (pid);
528+ return 0 ; // Parent
529+ }
530+
531+ // On détache complètement le fils du parent => peut importe qui fini en premier, l'autre ira jusqu'au bout :
532+ const pid_t sid = setsid ( );
533+ if ((pid_t )-1 == sid)
534+ {
535+ {
536+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
537+ ConsoleOutput::cerr ( ) << " ApplicationStats::initLogSession : échec de setsid : " << strerror (errno) << co_endl;
538+ }
539+ exit (0 );
540+ } // if ((pid_t)-1 == sid)
541+
542+ // On ouvre le fichier en lecture/écriture :
543+ FILE* file = fopen (fullFileName.c_str ( ), " r+" ); // Ne créé pas le fichier => on le créé ci-dessous si nécessaire :
544+ const bool created = NULL == file ? true : false ;
545+ file = NULL == file ? fopen (fullFileName.c_str ( ), " a+" ) : file;
546+ if (NULL == file)
547+ {
548+ try
549+ { // On peut avoir des exceptions de levées : chemin non traversable, ...
550+ File logFile (fullFileName);
551+ File dir = logFile.getPath ( );
552+ if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || (false == dir.isExecutable ( )) || (false == dir.isWritable ( )))
553+ {
554+ {
555+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
556+ ConsoleOutput::cerr ( ) << " Erreur, " << dir.getFullFileName ( ) << " n'est pas un répertoire existant avec les droits en écriture pour vous." << co_endl;
557+ }
558+ exit (0 );
559+ } // if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || ...
560+ if (false == logFile.isWritable ( ))
561+ {
562+ {
563+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
564+ ConsoleOutput::cerr ( ) << " Erreur lors de l'ouverture du fichier de logs " << fullFileName << " : absence de droits en écriture." << co_endl;
565+ }
566+ exit (0 );
567+ } // if (false == logFile.isWritable ( ))
568+ }
569+ catch (const Exception& exc)
570+ {
571+ {
572+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
573+ ConsoleOutput::cerr ( ) << " Erreur lors de l'ouverture du fichier de logs " << fullFileName << " : " << exc.getFullMessage ( ) << co_endl;
574+ }
575+ exit (0 );
576+ }
577+ catch (...)
578+ {
579+ }
580+
581+ {
582+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
583+ ConsoleOutput::cerr ( ) << " Erreur lors de l'ouverture du fichier de logs " << fullFileName << " : erreur non documentée." << co_endl;
584+ }
585+
586+ exit (0 );
587+ } // if (NULL == file)
588+
589+ // Obtenir le descripteur de fichier :
590+ int fd = fileno (file);
591+ if (-1 == fd)
592+ {
593+ {
594+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
595+ ConsoleOutput::cerr ( ) << " Erreur lors de l'ouverture du fichier de logs " << fullFileName << co_endl;
596+ }
597+ exit (0 );
598+ } // if (-1 == fd)
599+
600+ // Conférer aufichier les droits en écriture pour tous le monde si il vient d'être créé :
601+ if (true == created)
602+ {
603+ if (0 != fchmod (fd, S_IRWXU | S_IRWXG | S_IRWXO))
604+ {
605+ {
606+ TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
607+ ConsoleOutput::cerr ( ) << " Erreur lors du confèrement à autrui des droits en écriture sur le fichier de logs " << fullFileName << " : " << strerror (errno) << co_endl;
608+ }
609+ fclose (file);
610+ exit (0 );
611+
612+ } // if (0 != fchmod (fd)
613+ } // if (true == created)
614+
615+ return file;
616+ } // ApplicationStats::initLogSession
617+
618+
338619int ApplicationStats::readLogs (const string& fileName, map<string, size_t >& logs)
339620{
340621 File logFile (fileName);
@@ -360,7 +641,7 @@ int ApplicationStats::readLogs (FILE& file, const string& fileName, map<string,
360641 size_t line = 1 , count = 0 ;
361642 int flag = 0 ;
362643 char name [256 ];
363- while (2 == (flag = fscanf (&file, " %s\t %u " , name, &count)))
644+ while (2 == (flag = fscanf (&file, " %s\t %lu " , name, &count)))
364645 {
365646 line++;
366647 map<string, size_t >::iterator itl = logs.find (name);
@@ -369,7 +650,7 @@ int ApplicationStats::readLogs (FILE& file, const string& fileName, map<string,
369650 else
370651 (*itl).second += count;
371652 count = 0 ;
372- } // while (2 == fscanf (file, "%s\t%u ", name, &count))
653+ } // while (2 == fscanf (file, "%s\t%lu ", name, &count))
373654 if ((flag < 2 ) && (EOF != flag))
374655 {
375656 ConsoleOutput::cerr ( ) << " Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long )line << " : fichier probablement corrompu." << co_endl;
0 commit comments