Salut à tous !
Je poste ici le code source d'un petit utilitaire qui me permet de changer la date de création, de dernière modification ou de dernier accès d'un fichier, sous Windows.
Code écrit en C++/MFC, sous Visual C++ 6.0, pour toutes versions de Windows.
J'ai passé plus de temps à essayer de trouver un programme déjà tout fait ... que de l'écrire !
Coté source, j'ai vu celui de Nebula sur ce site, mais je cherchais un utilitaire un peu plus complet, qui soit à la fois simple et puissant (récursif), et puisse être appelé par un fichier "batch" (.bat), exactement comme le bon vieux touch.exe de MS-DOS... qui est devenu introuvable depuis !
Par rapport aux 2/3 utilitaires essayés, au moins, celui-ci est 100% gratuit, ne plante pas (!) et est donc meilleur pour la planète ;-))
Enfin, franchement, coté utilisation on perd moins de temps à taper la commande dans un shell DOS "à l'ancienne" que d'utiliser une interface fenêtrée et 36 clics à refaire à chaque fois...
Au fait, à quoi ça sert de changer la dates d'un fichier ?
C'est vrai, c'est assez rare... mais voici quelques exemples :
- remettre les photos numériques à la date de la prise de vue (en lisant les infos EXIF)
- taguer un ensemble de fichiers dans un répertoire
- corriger des problèmes de compilation (si la date du fichier projet est plus récente que celle des fichiers .c/.c++, il n'y a plus de compilation incrémentale)
- etc.
Source / Exemple :
// FTouch.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theApp;
using namespace std;
CString g_sFileFilter;
CTime g_aTime=0;
CTime g_mTime=0;
CTime g_cTime=0;
BOOL g_bRecurseDirs=FALSE;
// Output date format
const char g_szFmt[] = "%Y/%m/%d %H:%M:%S";
// Find a substring in a string without case sensitivity
LPCTSTR stristr( LPCTSTR string, LPCTSTR strCharSet )
{
char *pStr=NULL;
char szUpBuffer[1024]={0};
char szUpCharSet[127]={0};
UINT i;
for (i=0;i<strlen(string )+1;i++) szUpBuffer [i]=(char)toupper(string[i]);
for (i=0;i<strlen(strCharSet)+1;i++) szUpCharSet[i]=(char)toupper(strCharSet[i]);
pStr = strstr( szUpBuffer, szUpCharSet );
if (!pStr) return NULL;
i=pStr-szUpBuffer;
pStr=(char *)string+i;
return pStr;
}
// Function that tests
BOOL FileMatch(LPCTSTR pszFileName, LPCTSTR pszFileFilter)
{
int i0,i1,n0,n1,n;
char szDrive[2][_MAX_DRIVE]={0};
char szDir [2][_MAX_DIR ]={0};
char szName [2][_MAX_FNAME]={0};
char szExt [2][_MAX_EXT ]={0};
char szPath [_MAX_PATH ]={0};
LPCTSTR p=NULL;
_splitpath(pszFileName , szDrive[0], szDir[0], szName[0], szExt[0]);
_splitpath(pszFileFilter, szDrive[1], szDir[1], szName[1], szExt[1]);
// Remove the unnecessary point separator
if (szExt[0][0]=='.') strcpy(&szExt[0][0],&szExt[0][1]);
if (szExt[1][0]=='.') strcpy(&szExt[1][0],&szExt[1][1]);
// Check if directories match
i0=0; n0=strlen(szDir[0]);
i1=0; n1=strlen(szDir[1]);
n=max(n0,n1);
p=stristr(szDir[0],szDir[1]);
if (p) i0=p-szDir[0];
if (n1>0) while (i0<n && i1<n)
{
char c0=toupper(szDir[0][i0]);
char c1=toupper(szDir[1][i1]);
switch (c1)
{
case '*' : i0=n ; i1=n ; break;
case '?' : i0++ ; i1++ ; break;
default : if (c0!=c1) return FALSE;
i0++ ; i1++; break;
}
}
// Check if names match
i0=0; n0=strlen(szName[0]);
i1=0; n1=strlen(szName[1]);
n=max(n0,n1);
while (i0<n && i1<n)
{
char c0=toupper(szName[0][i0]);
char c1=toupper(szName[1][i1]);
switch (c1)
{
case '*' : i0=n ; i1=n ; break;
case '?' : i0++ ; i1++ ; break;
default : if (c0!=c1) return FALSE;
i0++ ; i1++; break;
}
}
// Check if extensions match
i0=0; n0=strlen(szExt[0]);
i1=0; n1=strlen(szExt[1]);
n=max(n0,n1);
while (i0<n && i1<n)
{
char c0=toupper(szExt[0][i0]);
char c1=toupper(szExt[1][i1]);
switch (c1)
{
case '*' : i0=n ; i1=n ; break;
case '?' : i0++ ; i1++ ; break;
default : if (c0!=c1) return FALSE;
i0++ ; i1++; break;
}
}
return TRUE;
}
// Base function that changes the date stamps of a file
BOOL Touch(LPCTSTR pszPath)
{
CFileStatus status;
CFile::GetStatus(pszPath,status);
if (g_aTime>0) status.m_atime = g_aTime;
if (g_cTime>0) status.m_ctime = g_cTime;
if (g_mTime>0) status.m_mtime = g_mTime;
CFile::SetStatus(pszPath,status);
TRACE("c:%s m:%s a:%s \t%s\n",
status.m_ctime.Format(g_szFmt),
status.m_mtime.Format(g_szFmt),
status.m_atime.Format(g_szFmt), pszPath);
return TRUE;
}
// Recursive function that changes the date stamps of files
// matching the file filter. Returns the number of files touched
int TouchFiles(LPCTSTR pszPath,LPCTSTR pszFilter)
{
int n=0;
if (!SetCurrentDirectory(pszPath))
return n;
CFileFind FileFind;
if ( !FileFind.FindFile("*.*") )
return n;
BOOL bNext;
do
{
bNext = FileFind.FindNextFile();
if (FileFind.IsDots()) continue;
CString sPath = FileFind.GetFilePath();
// On ajoute les fichier à la liste des fichiers
if (FileFind.IsDirectory())
{
if (g_bRecurseDirs)
n+= TouchFiles(sPath,pszFilter);
}
else
{
if (!FileMatch(sPath,pszFilter))
continue;
if (Touch(sPath)) n++;
}
} while (bNext);
// TRACE("%s : %d datafiles found\n",pszPath,n);
return n;
}
// Utility function that decodes times expressed in the command line
// If error, returns CTime(0) <=> Jan. 1st 1970, 01:00:00
CTime ExtractTime(LPCTSTR pszParam)
{
CString s = pszParam;
// Replace all exotic characters by whites
s.Replace("/"," ");
s.Replace(":"," ");
s.Replace("_"," ");
s.Replace("@"," ");
s.Replace(" "," ");
// Take current time as default
CTime t=CTime::GetCurrentTime();
int nYear = t.GetYear();
int nMonth= t.GetMonth();
int nDay = t.GetDay();
int nHour = t.GetHour();
int nMin = t.GetMinute();
int nSec = t.GetSecond();
// Break-up the expression with whites
int n = strlen(s);
if (!strstr(s," ")) switch(n)
{
case 0 : break;
case 6 :
s.Insert( 4," ");
s.Insert( 2," ");
break;
case 8 :
s.Insert( 6," ");
s.Insert( 4," ");
break;
case 12 :
s.Insert(10," ");
s.Insert( 8," ");
s.Insert( 6," ");
s.Insert( 4," ");
s.Insert( 2," ");
sscanf(s,"%d",&n);
if (n>18 && n<21) // Are the seconds or the century missing here ? Bug if (year%100)=19 or 20 !!!
s = s.Left(2)+s.Right(14);
break;
case 14 :
s.Insert(12," ");
s.Insert(10," ");
s.Insert( 8," ");
s.Insert( 6," ");
s.Insert( 4," ");
break;
}
// Attribute years, months, days, hours, minutes, seconds
n = strlen(s);
switch(n)
{
case 0 : return CTime(nYear,nMonth,nDay,nHour,nMin,nSec);
case 1 : return CTime(0);
case 8 :
case 10:
n=sscanf(s,"%d %d %d",&nYear,&nMonth,&nDay);
if (n!=3) return CTime(0);
break;
case 14 :
case 16 :
n=sscanf(s,"%d %d %d %d %d",&nYear,&nMonth,&nDay,&nHour,&nMin);
if (n!=5) return CTime(0);
nSec=0;
break;
case 17 :
case 19 :
n=sscanf(s,"%d %d %d %d %d %d",&nYear,&nMonth,&nDay,&nHour,&nMin,&nSec);
if (n!=6) return CTime(0);
break;
default :
return CTime(0);
}
// Check validity ranges
if (nYear>=0 && nYear<1000 && nYear>50) nYear+=1900; else
if (nYear>=0 && nYear<1000 && nYear<50) nYear+=2000;
if (nYear < 0 || nYear >3000) return CTime(0);
if (nMonth<=0 || nMonth>12) return CTime(0);
if (nDay <=1 || nDay >31) return CTime(0);
if (nHour < 0 || nHour >23) return CTime(0);
if (nMin < 0 || nMin >59) return CTime(0);
if (nSec < 0 || nSec >59) return CTime(0);
// Build the time value to return
t = CTime(nYear,nMonth,nDay,nHour,nMin,nSec);
return t;
}
// Function that tests outputs (examples)
CString DateEx(LPCTSTR psz)
{
CTime t = ExtractTime(psz);
CString s=StringFormat("%-20s : %s",psz,t.Format(g_szFmt));
return s;
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
char drive[_MAX_DRIVE]={0};
char dir [_MAX_DIR ]={0};
char fname[_MAX_FNAME]={0};
char ext [_MAX_EXT ]={0};
char szBuffer[MAX_PATH]={0};
// Handle command-line flags
int nArgc = argc;
for (int i=argc-1;i>=1;i--)
{
LPCTSTR pszArg=argv[i];
if ( strlen(pszArg)<2 ) continue;
if ( pszArg[0]!='/' && pszArg[0]!='-' )
{
if ( g_sFileFilter=="") g_sFileFilter = pszArg;
}
else
{
BOOL bFound=FALSE;
if ( _strnicmp(&pszArg[1],"A=",2)==0) { g_aTime = ExtractTime(&pszArg[3]); if (g_aTime>0) TRACE("aTime=%s\n",g_aTime.Format(g_szFmt)); } else
if ( _strnicmp(&pszArg[1],"M=",2)==0) { g_mTime = ExtractTime(&pszArg[3]); if (g_mTime>0) TRACE("mTime=%s\n",g_mTime.Format(g_szFmt)); } else
if ( _strnicmp(&pszArg[1],"C=",2)==0) { g_cTime = ExtractTime(&pszArg[3]); if (g_cTime>0) TRACE("cTime=%s\n",g_cTime.Format(g_szFmt)); } else
if ( _stricmp (&pszArg[1],"A" )==0) { g_aTime = CTime::GetCurrentTime(); TRACE("aTime=%s\n",g_aTime.Format(g_szFmt)); } else
if ( _stricmp (&pszArg[1],"M" )==0) { g_mTime = CTime::GetCurrentTime(); TRACE("mTime=%s\n",g_mTime.Format(g_szFmt)); } else
if ( _stricmp (&pszArg[1],"C" )==0) { g_cTime = CTime::GetCurrentTime(); TRACE("cTime=%s\n",g_cTime.Format(g_szFmt)); } else
if ( _stricmp (&pszArg[1],"S" )==0) { g_bRecurseDirs = TRUE; } else
if (bFound)
{
// Push command-line params
for (int j=i;j<argc;j++)
argv[j] = (j<argc-1) ? argv[j+1] : NULL;
argc--;
}
else
{
TRACE("%s : unknown flag !\n",pszArg);
}
}
}
if (g_sFileFilter!="")
{
char szDrive [_MAX_DRIVE ]={0};
char szDir [_MAX_DIR ]={0};
char szName [_MAX_FNAME ]={0};
char szExt [_MAX_EXT ]={0};
char szInitFileDir[_MAX_PATH]={0};
char szFileFilter [_MAX_PATH]={0};
_splitpath(g_sFileFilter, szDrive, szDir, szName, szExt);
_makepath (szInitFileDir, szDrive, szDir, NULL, NULL);
_makepath (szFileFilter , NULL, NULL, szName, szExt);
if (!szInitFileDir[0]) GetCurrentDirectory(_MAX_PATH,szInitFileDir);
if (!szFileFilter[0]) strcpy(szFileFilter,"*.*");
TouchFiles(szInitFileDir,szFileFilter);
}
else
{
TRACE("File touch utility\n");
TRACE("\tUsage : FTouch <filespec> /C /M /A /C=<date> /M=<date> /A=<date> \n");
TRACE("\tDateFormats: %s\n",DateEx("20061129175900"));
TRACE("\t %s\n",DateEx("20061129"));
TRACE("\t %s\n",DateEx("061129"));
TRACE("\t %s\n",DateEx("2006_11_29"));
TRACE("\t %s\n",DateEx("2006_11_29_17_59_00"));
TRACE("\t %s\n",DateEx("2006/11/29_17:59"));
TRACE("\t %s\n",DateEx("06/11/29_17:59:00"));
}
}
#ifdef _DEBUG
printf("\nPress a key to exit program...");
getchar();
#endif
return nRetCode;
}
Conclusion :
Voilà, c'est mon 1er post.
Je le publie juste pour aider les confrères qui ont les mêmes besoins que moi : retrouver l'équivalent du bon vieux "touch.exe"...
Comme vous le verrez, le code n'est pas révolutionnaire, mais simple et de bon-goût.
Aussi, je pense que ce code intéressera plutôt les débutants.
Désolé, les commentaires sont en anglais (contexte professionnel), mais je pense qu'en tant que développeurs, ça ne doit pas être un problème insurmontable...
Ah, dernière chose : je ne me souvenais plus de la syntaxe de la ligne de commande du programme original, alors j'en ai recréée une qui est simple et puissante.
Le programme lancé seul affiche cette syntaxe de ligne de commande.
Bonne lecture et au plaisir !
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.