Skip to content

Commit e965c1f

Browse files
author
Charles PIGNEROL
committed
Version 6.11.0. ApplicationStats class to record the number of monthly usages of an application per user.
1 parent 9452fca commit e965c1f

File tree

6 files changed

+295
-3
lines changed

6 files changed

+295
-3
lines changed

cmake/version.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#
44

55
set (TK_UTIL_MAJOR_VERSION "6")
6-
set (TK_UTIL_MINOR_VERSION "10")
7-
set (TK_UTIL_RELEASE_VERSION "3")
6+
set (TK_UTIL_MINOR_VERSION "11")
7+
set (TK_UTIL_RELEASE_VERSION "0")
88
set (TK_UTIL_VERSION ${TK_UTIL_MAJOR_VERSION}.${TK_UTIL_MINOR_VERSION}.${TK_UTIL_RELEASE_VERSION})
99

1010
set (TK_UTIL_SCRIPTING_MAJOR_VERSION ${TK_UTIL_MAJOR_VERSION})

src/TkUtil/ApplicationStats.cpp

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#include "TkUtil/AnsiEscapeCodes.h"
2+
#include "TkUtil/ApplicationStats.h"
3+
#include "TkUtil/Exception.h"
4+
#include "TkUtil/File.h"
5+
#include "TkUtil/UTF8String.h"
6+
7+
#include <TkUtil/Date.h>
8+
#include <TkUtil/File.h>
9+
#include <TkUtil/Threads.h>
10+
#include <TkUtil/UserData.h>
11+
12+
#include <map>
13+
14+
#include <assert.h>
15+
#include <errno.h>
16+
#include <sys/file.h> // flock
17+
#include <stdio.h> // fopen, fseek, fscanf, fprintf
18+
#include <string.h> // strerror
19+
#include <sys/types.h>
20+
#include <unistd.h> // fork, setsid
21+
22+
USING_STD
23+
24+
BEGIN_NAMESPACE_UTIL
25+
26+
27+
ApplicationStats::ApplicationStats ( )
28+
{
29+
assert (0 && "ApplicationStats::ApplicationStats is not allowed.");
30+
} // ApplicationStats::ApplicationStats
31+
32+
33+
ApplicationStats::ApplicationStats (const ApplicationStats&)
34+
{
35+
assert (0 && "ApplicationStats::ApplicationStats is not allowed.");
36+
} // ApplicationStats::ApplicationStats
37+
38+
39+
ApplicationStats& ApplicationStats::operator = (const ApplicationStats&)
40+
{
41+
assert (0 && "ApplicationStats::assignment operator is not allowed.");
42+
return *this;
43+
} // ApplicationStats::operator =
44+
45+
46+
ApplicationStats::~ApplicationStats ( )
47+
{
48+
assert (0 && "ApplicationStats::~ApplicationStats is not allowed.");
49+
} // ApplicationStats::~ApplicationStats
50+
51+
52+
void ApplicationStats::logUsage (const string& appName, const string& logDir)
53+
{
54+
// En vue de ne pas altérer le comportement de l'application tout est effectuée dans un processus fils.
55+
// Par ailleurs on quitte le processus fils par return et non exit pour passer dans le destructeur des variables automatiques créées (TermAutoStyle, ...).
56+
errno = 0;
57+
const pid_t pid = fork ( );
58+
if ((pid_t)-1 == pid)
59+
{
60+
ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : échec de fork : " << strerror (errno) << co_endl;
61+
return;
62+
} // if ((pid_t)-1 == pid)
63+
if (0 != pid)
64+
return; // Parent
65+
66+
// On détache complètement le fils du parent => peut importe qui fini en premier, l'autre ira jusqu'au bout :
67+
const pid_t sid = setsid ( );
68+
if ((pid_t)-1 == sid)
69+
{
70+
ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : échec de setsid : " << strerror (errno) << co_endl;
71+
return;
72+
} // if ((pid_t)-1 == sid)
73+
74+
TermAutoStyle as (cerr, AnsiEscapeCodes::blueFg);
75+
if ((true == appName.empty ( )) || (true == logDir.empty ( )))
76+
{
77+
ConsoleOutput::cerr ( ) << "ApplicationStats::logUsage : nom d'application ou répertoire des logs non renseigné (" << appName << "/" << logDir << ")." << co_endl;
78+
return;
79+
} // if ((true == appName.empty ( )) || (true == logDir.empty ( )))
80+
81+
// Le nom du fichier :
82+
const Date date;
83+
const string user (UserData (true).getName ( ));
84+
UTF8String fileName;
85+
fileName << logDir << "/" << appName << "_" << IN_UTIL setw (4) << date.getYear ( ) << setw (2) << (unsigned long)date.getMonth ( ) << ".logs";
86+
87+
// On ouvre le fichier en lecture/écriture :
88+
FILE* file = fopen (fileName.utf8 ( ).c_str ( ), "r+"); // Ne créé pas le fichier => on le créé ci-dessous si nécessaire :
89+
file = NULL == file ? fopen (fileName.utf8 ( ).c_str ( ), "a+") : file;
90+
if (NULL == file)
91+
{
92+
try
93+
{ // On peut avoir des exceptions de levées : chemin non traversable, ...
94+
File dir (logDir);
95+
if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || (false == dir.isExecutable ( )) || (false == dir.isWritable ( )))
96+
{
97+
ConsoleOutput::cerr ( ) << "Erreur, " << logDir << " n'est pas un répertoire existant avec les droits en écriture pour vous." << co_endl;
98+
return;
99+
} // if ((false == dir.exists ( )) || (false == dir.isDirectory ( )) || ...
100+
File logFile (fileName.utf8 ( ));
101+
if (false == logFile.isWritable ( ))
102+
{
103+
ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << " : absence de droits en écriture." << co_endl;
104+
return;
105+
} // if (false == logFile.isWritable ( ))
106+
}
107+
catch (const Exception& exc)
108+
{
109+
ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << " : " << exc.getFullMessage ( ) << co_endl;
110+
return;
111+
}
112+
catch (...)
113+
{
114+
}
115+
116+
ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << " : erreur non documentée." << co_endl;
117+
118+
return;
119+
} // if (NULL == file)
120+
121+
// Obtenir le descripteur de fichier :
122+
int fd = fileno (file);
123+
if (-1 == fd)
124+
{
125+
ConsoleOutput::cerr ( ) << "Erreur lors de l'ouverture du fichier de logs " << fileName << co_endl;
126+
return;
127+
} // if (-1 == fd)
128+
129+
// Appliquer un verrou exclusif sur le fichier de logs :
130+
errno = 0;
131+
if (0 != flock (fd, LOCK_EX))
132+
{
133+
ConsoleOutput::cerr ( ) << "Erreur lors du verrouillage du fichier de logs " << fileName << " : " << strerror (errno) << co_endl;
134+
fclose (file);
135+
return;
136+
} // if (0 != flock (fd, LOCK_EX))
137+
138+
// Lecture et actualisation des logs existants :
139+
map<string, size_t> logs;
140+
char name [256];
141+
size_t count = 0, line = 1;
142+
bool found = false;
143+
int flag = 0;
144+
errno = 0;
145+
while (2 == (flag = fscanf (file, "%s\t%u", name, &count)))
146+
{
147+
line++;
148+
if (name == user)
149+
{
150+
found = true;
151+
count++;
152+
} // if (name == user)
153+
logs.insert (pair<string, size_t> (name, count));
154+
count = 0;
155+
} // while (2 == fscanf (file, "%s\t%u", name, &count))
156+
if (0 != errno)
157+
{
158+
ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << " : " << strerror (errno) << co_endl;
159+
fclose (file);
160+
return;
161+
} // if (0 != errno)
162+
else if ((flag < 2) && (EOF != flag))
163+
{
164+
ConsoleOutput::cerr ( ) << "Erreur lors de la lecture du fichier de logs " << fileName << " en ligne " << (unsigned long)line << " : fichier probablement corrompu." << co_endl;
165+
fclose (file);
166+
return;
167+
} // if (flag < 2)
168+
if (false == found)
169+
logs.insert (pair<string, size_t> (user, 1));
170+
171+
// Ecriture des logs actualisés :
172+
errno = 0;
173+
if (0 != fseek (file, 0, SEEK_SET))
174+
{
175+
ConsoleOutput::cerr ( ) << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl;
176+
fclose (file);
177+
return;
178+
} // if (0 != fseek (file, 0, SEEK_SET))
179+
180+
for (map<string, size_t>::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++)
181+
{
182+
if (fprintf (file, "%s\t%u\n", (*itl).first.c_str ( ), (*itl).second) < 0)
183+
{
184+
ConsoleOutput::cerr ( ) << "Erreur lors de la réécriture du fichier de logs " << fileName << "."<< co_endl;
185+
fclose (file);
186+
return;
187+
}
188+
} // for (map<string, size_t>::const_iterator itl = logs.begin ( ); logs.end ( ) != itl; itl++)
189+
errno = 0;
190+
if (0 != fflush (file))
191+
{
192+
ConsoleOutput::cerr ( ) << "Erreur lors de la réécriture du fichier de logs " << fileName << " : " << strerror (errno) << co_endl;
193+
fclose (file);
194+
return;
195+
} // if (0 != fflush (file))
196+
197+
// Libération du verrou :
198+
errno = 0;
199+
if (0 != flock (fd, LOCK_UN))
200+
{
201+
ConsoleOutput::cerr ( ) << "Erreur lors du déverrouillage du fichier de logs " << fileName << " : " << strerror (errno) << co_endl;
202+
fclose (file);
203+
} // if (0 != flock (fd, LOCK_UN))
204+
} // ApplicationStats::logUsage
205+
206+
207+
END_NAMESPACE_UTIL
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#ifndef APPLICATION_STATS_H
2+
#define APPLICATION_STATS_H
3+
4+
#include <TkUtil/util_config.h>
5+
6+
#include <string>
7+
8+
9+
BEGIN_NAMESPACE_UTIL
10+
11+
12+
/**
13+
* Classe de services destinés à enregistrer des logs pour statistiques sur l'utilisation d'applications.
14+
* Ces services sont à vocation non bloquante, ils ne doivent pas occasionner la moindre gêne à l'application utilisatrice,
15+
* même en cas de défaillance : ça marche, tant mieux, ça ne marche pas, tant pis !
16+
* @author Charles PIGNEROL, CEA/DAM/DSSI
17+
* @since 6.11.0
18+
*/
19+
class ApplicationStats
20+
{
21+
public :
22+
23+
/**
24+
* <U><B>Les fichiers simplifié d'utilisation d'application.</B></U><BR>
25+
* <U>Nom du fichier :</U> NomApplication_YYYYMM où YYYY représente l'année et MM le mois en cours,<BR>
26+
* <U>Format :</U><BR>
27+
* utilisateur nbutilisations
28+
*/
29+
//@{ Fichier simplifié d'utilisation d'applications
30+
31+
/**
32+
* Ajoute une utilisation de cette application à l'utilisateur courrant. L'opération se fait dans un processus détaché.
33+
* Toute erreur rencontrée est affichée dans la console de lancement de l'application.
34+
* @param appName est le nom de l'application
35+
* @param logDir est le répertoire où sont stockés les fichiers de logs
36+
*/
37+
static void logUsage (const std::string& appName, const std::string& logDir);
38+
39+
//@} Fichier simplifié d'utilisation d'applications
40+
41+
42+
private :
43+
44+
/**
45+
* Constructeurs/Destructeur : interdits.
46+
*/
47+
ApplicationStats ( );
48+
ApplicationStats (const ApplicationStats&);
49+
ApplicationStats& operator = (const ApplicationStats&);
50+
~ApplicationStats ( );
51+
}; // class ApplicationStats
52+
53+
54+
END_NAMESPACE_UTIL
55+
56+
57+
#endif // APPLICATION_STATS_H

src/tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ include (${GUIToolkitsVariables_CMAKE_DIR}/common.cmake)
33
include (${GUIToolkitsVariables_CMAKE_DIR}/workarounds.cmake)
44

55
set (ALL_EXECUTABLES
6-
ansi_esc_codes canceled_threads conversions date dir_list exceptions
6+
ansi_esc_codes app_usage canceled_threads conversions date dir_list exceptions
77
fileExtractor fileinfos fileopts hostinfos joinable locale
88
logs memory modify_script process remoteProcess removefile script_tags
99
scripting_logs thread_manager thread_pool timer tmpfile

src/tests/app_usage.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Test du stockage de stats d'utilisation d'un produit.
2+
#include "TkUtil/ApplicationStats.h"
3+
4+
#include <iostream>
5+
#include <string>
6+
7+
using namespace TkUtil;
8+
using namespace std;
9+
10+
11+
int main (int argc, char* argv [])
12+
{
13+
if (argc < 3)
14+
{
15+
cerr << "Syntaxe : " << argv [0] << " NomApp RépertoireLogs" << endl;
16+
return -1;
17+
} // if (argc < 3)
18+
19+
ApplicationStats::logUsage (argv [1], argv [2]);
20+
21+
return 0;
22+
} // main

versions.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Version 6.11.0 : 07/02/25
2+
=================
3+
4+
Classe ApplicationStats + test pour loguer dans un système de fichiers les utilisateurs d'applications.
5+
6+
17
Version 6.10.3 : 06/12/24
28
=================
39

0 commit comments

Comments
 (0)