|
/* (c) Copyright Robin Mills Software Consultancy 2007- |
|
San Jose, CA USA. All rights reserved |
|
http://www.clanmills.com [email protected] |
|
----------------------------------------------------------------- |
|
* File : myTouch.cpp |
|
* Author : Robin Mills |
|
* Date : 20071019 (ccyymmdd) |
|
----------------------------------------------------------------- |
|
|
|
* Purpose: |
|
Report/Modify file timestamps on MacOSX |
|
|
|
* Useage : |
|
myTouch [-option[=date]]+ file... |
|
option := { -createDate | -contentModDate | -attributeModDate | -accessDate | -backupDate | -all } |
|
date := { -now | [CC]YYMMhhdd[.ss] } |
|
|
|
If you use an option more that once, the last definition will be used. |
|
myTouch -createDate=-now -createDate=200102030405 Image.jpg |
|
Set the createDate of Image.jpg to Feb 03, 2001 |
|
|
|
* Example: |
|
myTouch Image.jpg Report all dates of Image.jpg |
|
myTouch -createDate Image.jpg Report the createDate of Image.jpg |
|
myTouch -createDate -contentModDate Image.jpg Report the createDate and contentModDate of Image.jpg |
|
myTouch -createDate=200710161039 Image.jpg Set the createDate of Image.jpg |
|
myTouch -all=-now *.jpg Set all time stamps to current time for all .jpg files |
|
|
|
* Description: |
|
This program is different from the system touch command |
|
touch enables you to modify the UNIX times atime, ctime and mtime |
|
myTouch enables you to report/modify the MacOSX time stamps on the file object |
|
|
|
* Restrictions: |
|
The following restrictions seem to be imposed by FSSetCatalogInfo |
|
|
|
createDate is the birthtime of the file (and contentModDate cannot be before createDate) |
|
So: |
|
1 When you set createDate, contentModDate will be reset forward if necessary |
|
2 When you set contentModDate, createDate will be set back if necessary |
|
3 These rules seem random to me because they are not enforced for the other 3 timestamps! |
|
|
|
you cannot set -attributeModDate=date because changing any of the attributes |
|
of a file causes this date to be set by the OS (even attributeModDate!) |
|
|
|
* Ideas to extend this: |
|
1 add a time definition based on file -option=filename to copy datestamps from another file |
|
2 allow multiple definitions of option/dates and files |
|
Example myTouch -option=date file ... -option=date file... |
|
3 Implement on Linux and Windows |
|
|
|
* More information: |
|
http://www.clanmills.com/articles/macosx/myTouch |
|
|
|
* Revision history at bottom of file |
|
|
|
----------------------------------------------------------------- |
|
*/ |
|
|
|
#include <Carbon/Carbon.h> |
|
|
|
#include <iostream> |
|
#include <strings.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
|
|
#define lengthof(x) (sizeof(x)/sizeof(x[0])) |
|
static char* titles[] = { "-all" , "-createDate" , "-contentModDate" , "-attributeModDate" , "-accessDate" , "-backupDate" } ; |
|
|
|
// prototypes and forward declarations |
|
class Option ; |
|
int syntax(bool bAll) ; |
|
bool parseOptionDate(const char* o,char** option,char** date) ; |
|
int getOption(const char* arg) ; |
|
OSStatus reportDate(UInt8* path,int option) ; |
|
CFDateRef getDate(const char* s) ; |
|
OSStatus setDate(UInt8* path,int option,CFDateRef date) ; |
|
bool fexist(const char* path) ; |
|
bool parseOption(const char* s,Option* Options,bool* pbOK) ; |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* syntax - report syntax of program */ |
|
/* ----------------------------------------------------------- */ |
|
int syntax(bool bAll) |
|
{ |
|
fprintf(stderr,"usage : myTouch [option[=date]]+ file ...\n") ; |
|
if ( bAll ) |
|
{ |
|
fprintf(stderr,"option : ") ; |
|
|
|
for ( int i = 0 ; i < lengthof (titles) ; i++ ) |
|
{ |
|
if ( i != 0 ) fprintf(stderr," | ") ; |
|
fprintf(stderr,"%s",titles[i]) ; |
|
} |
|
fprintf(stderr,"\n") ; |
|
fprintf(stderr,"date : [-now | [CC]YYDDMMhh[.ss] ]\n") ; |
|
fprintf(stderr,"example: myTouch -all=-now Image.jpg\n") ; |
|
} |
|
|
|
return -1 ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* parseOptionDate - parse -option=date into two tokens */ |
|
/* ----------------------------------------------------------- */ |
|
bool parseOptionDate(const char* o,char** option,char** date) |
|
{ |
|
bool result = false ; |
|
|
|
*date = NULL ; |
|
*option = NULL ; |
|
|
|
if ( !o || o[0] != '-' ) return result ; |
|
|
|
char* copy = strdup(o) ; |
|
if ( !copy ) return result ; |
|
|
|
result = true ; |
|
|
|
char* equal = strchr(copy,'=') ; |
|
if ( equal ) |
|
{ |
|
*equal = 0 ; |
|
*option = strdup(copy) ; |
|
*date = strdup(equal+1); |
|
} else { |
|
*option = strdup(copy) ; |
|
} |
|
|
|
// cleanup |
|
free((char*) copy) ; |
|
|
|
return result ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* getOption - which options? */ |
|
/* ----------------------------------------------------------- */ |
|
int getOption(const char* arg) |
|
{ |
|
for ( int i = 0 ; i < lengthof(titles) ; i++ ) |
|
if ( strcasecmp(arg,titles[i]) == 0 ) |
|
return i ; |
|
|
|
return -1 ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* reportDate - report the date of a file */ |
|
/* ----------------------------------------------------------- */ |
|
OSStatus reportDate(UInt8* path,int option) |
|
{ |
|
if ( option < 1 || option >= lengthof(titles)) return paramErr ; |
|
|
|
// get the fsRef for the path |
|
FSRef fsRef ; |
|
Boolean isDirectory ; |
|
FSCatalogInfo fsCatalogInfo ; |
|
|
|
OSStatus err = FSPathMakeRef ( path,&fsRef,&isDirectory) ; |
|
if ( !err ) err = FSGetCatalogInfo (&fsRef,kFSCatInfoAllDates,&fsCatalogInfo,NULL,NULL,NULL) ; |
|
|
|
UTCDateTime* myDateTime = option == 1 ? &fsCatalogInfo.createDate |
|
: option == 2 ? &fsCatalogInfo.contentModDate |
|
: option == 3 ? &fsCatalogInfo.attributeModDate |
|
: option == 4 ? &fsCatalogInfo.accessDate |
|
: option == 5 ? &fsCatalogInfo.backupDate |
|
: NULL |
|
; |
|
|
|
// convert the time to a string and report it |
|
if ( !err ) |
|
{ |
|
CFAbsoluteTime absoluteTime ; |
|
UCConvertUTCDateTimeToCFAbsoluteTime(myDateTime,&absoluteTime) ; |
|
CFDateFormatterRef dateFormatter = CFDateFormatterCreate(NULL,NULL, kCFDateFormatterLongStyle, kCFDateFormatterLongStyle) ; |
|
CFStringRef dateString = dateFormatter |
|
? CFDateFormatterCreateStringWithAbsoluteTime(NULL,dateFormatter,absoluteTime) |
|
: NULL ; |
|
|
|
if ( dateString ) |
|
{ |
|
char buffer[1000] ; |
|
CFStringGetCString(dateString,buffer,sizeof buffer,kCFStringEncodingUTF8) ; |
|
char title[50] ; |
|
sprintf(title,"%s ",titles[option]) ; |
|
title[17] = 0 ; |
|
printf("%s : %s => %s\n",path,title,buffer ) ; |
|
CFRelease(dateString) ; |
|
} |
|
|
|
if ( dateFormatter ) CFRelease(dateFormatter) ; |
|
} |
|
return err ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* getDate - parse a date string */ |
|
/* ----------------------------------------------------------- */ |
|
CFDateRef getDate(const char* s) |
|
{ |
|
if ( strcasecmp(s,"-now") == 0 ) return CFDateCreate(NULL,CFAbsoluteTimeGetCurrent()) ; |
|
|
|
// what type of formatting is this? Syntax: [CC]YYDDMMhh[.ss] |
|
int l = strlen(s) ; // length of input string |
|
int f = -1 ; // which format (0,1,2 or 3) |
|
|
|
const char* dateFormats[] = { "yyyyMMddHHmm.ss", "yyyyMMddHHmm" , "yyMMddHHmm.ss" , "yyMMddHHmm" } ; |
|
for ( int i = 0 ; f < 0 && i < lengthof(dateFormats) ; i++ ) |
|
if ( l == strlen(dateFormats[i]) ) |
|
f = i ; |
|
const char* dateFormat = f != -1 ? dateFormats[f] : NULL ; |
|
|
|
// create a formatter |
|
CFLocaleRef currentLocale = CFLocaleCopyCurrent(); |
|
CFDateFormatterRef dateFormatter = CFDateFormatterCreate(NULL, currentLocale, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle); |
|
CFStringRef dateString = CFStringCreateWithBytes (NULL,(UInt8*)s,l,kCFStringEncodingUTF8,false) ; |
|
CFStringRef dateFormatString= dateFormat ? CFStringCreateWithBytes (NULL,(UInt8*)dateFormat,strlen(dateFormat),kCFStringEncodingUTF8,false) : NULL ; |
|
if ( dateFormat ) CFDateFormatterSetFormat(dateFormatter, dateFormatString ); |
|
|
|
// decode the date |
|
CFDateRef date = dateFormat ? CFDateFormatterCreateDateFromString(NULL,dateFormatter,dateString,NULL) : NULL ; |
|
|
|
// cleanup |
|
CFRelease(currentLocale); |
|
CFRelease(dateString); |
|
CFRelease(dateFormatter); |
|
if ( dateFormatString ) CFRelease(dateFormatString); |
|
|
|
return date ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* setDate - set the date on a filename */ |
|
/* ----------------------------------------------------------- */ |
|
OSStatus setDate(UInt8* path,int option,CFDateRef date) |
|
{ |
|
if ( option < 1 || option >= lengthof(titles)) return paramErr ; |
|
|
|
// get the fsRef for the path |
|
FSRef fsRef ; |
|
Boolean isDirectory ; |
|
FSCatalogInfo fsCatalogInfo ; |
|
|
|
OSStatus err = FSPathMakeRef ( path,&fsRef,&isDirectory) ; |
|
if ( !err ) err = FSGetCatalogInfo (&fsRef,kFSCatInfoAllDates,&fsCatalogInfo,NULL,NULL,NULL) ; |
|
|
|
UTCDateTime* myDateTime = option == 1 ? &fsCatalogInfo.createDate |
|
: option == 2 ? &fsCatalogInfo.contentModDate |
|
: option == 3 ? &fsCatalogInfo.attributeModDate |
|
: option == 4 ? &fsCatalogInfo.accessDate |
|
: option == 5 ? &fsCatalogInfo.backupDate |
|
: NULL |
|
; |
|
|
|
// set the date |
|
if ( !err ) |
|
{ |
|
CFAbsoluteTime absoluteTime = CFDateGetAbsoluteTime (date) ; |
|
UCConvertCFAbsoluteTimeToUTCDateTime(absoluteTime,myDateTime) ; |
|
|
|
// save the contentModDate |
|
CFAbsoluteTime contentModTime ; |
|
UCConvertUTCDateTimeToCFAbsoluteTime (&fsCatalogInfo.contentModDate,&contentModTime) ; |
|
|
|
// if we're setting createData, we also have to set contentModDate! |
|
if ( option == 1 ) { |
|
UCConvertCFAbsoluteTimeToUTCDateTime(absoluteTime,&fsCatalogInfo.contentModDate) ; |
|
} |
|
err = FSSetCatalogInfo (&fsRef,kFSCatInfoAllDates,&fsCatalogInfo) ; |
|
|
|
// now restore the contentModDate |
|
if ( !err && option == 1 && absoluteTime < contentModTime ) { |
|
UCConvertCFAbsoluteTimeToUTCDateTime(contentModTime,&fsCatalogInfo.contentModDate) ; |
|
err = FSSetCatalogInfo (&fsRef,kFSCatInfoContentMod,&fsCatalogInfo) ; |
|
} |
|
} |
|
|
|
return err ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* class Option - parse result of "-option[=date]" */ |
|
/* ----------------------------------------------------------- */ |
|
class Option |
|
{ |
|
public: |
|
Option() : m_date(NULL) , m_report(false) {} ; |
|
virtual ~Option() { setDate(NULL) ; } |
|
|
|
void setDate(CFDateRef date) |
|
{ |
|
if ( date ) CFRetain(date) ; |
|
if ( m_date ) { |
|
CFRelease(m_date) ; |
|
m_date = NULL ; |
|
} |
|
|
|
if ( date ) { |
|
m_date = date ; |
|
} |
|
} |
|
CFDateRef getDate() { return m_date ; } |
|
|
|
void setReport(bool report) { m_report = report ; } |
|
bool getReport(void) { return m_report ; } |
|
|
|
private: |
|
CFDateRef m_date ; |
|
bool m_report ; |
|
} ; |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* fexist - does a filename exist (link,dir or file) */ |
|
/* ----------------------------------------------------------- */ |
|
bool fexist(const char* path) |
|
{ |
|
struct stat buff ; |
|
return stat(path,&buff) == 0 ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* parseOption -option[=date] */ |
|
/* ----------------------------------------------------------- */ |
|
bool parseOption(const char* s,Option* Options,bool* pbOK) |
|
{ |
|
// is it a file? |
|
if ( !s || fexist(s) ) return false ; |
|
|
|
char* o = NULL ; |
|
char* d = NULL ; |
|
bool bOK = parseOptionDate(s,&o,&d) ; |
|
int option = bOK && o ? getOption(o) : -1 ; |
|
|
|
if ( !bOK ) fprintf(stderr,"%s: %s\n",s[0]=='-' ? "illegal argument" : "unknown file" ,s) ; |
|
|
|
if ( bOK && !o ) { |
|
fprintf(stderr,"no option found %s\n",s) ; |
|
bOK = false ; |
|
} |
|
|
|
|
|
// found an option? |
|
if ( bOK && option == -1 ) |
|
{ |
|
fprintf(stderr,"unknown option %s\n",o) ; |
|
bOK = false ; |
|
} |
|
|
|
// found a date? |
|
CFDateRef date = NULL ; |
|
if ( bOK && d ) { |
|
date = getDate(d) ; |
|
if ( !date ) { |
|
fprintf(stderr,"date syntax error %s\n",d) ; |
|
bOK = false ; |
|
} |
|
} |
|
|
|
// update the Options array |
|
if ( bOK ) { |
|
if ( !option ) // -all |
|
{ |
|
for ( int i = 1 ; i < lengthof(titles) ; i++ ) |
|
{ |
|
Options[i].setDate(date) ; |
|
Options[i].setReport(true) ; |
|
} |
|
} else { |
|
Options[option].setDate(date) ; |
|
Options[option].setReport(true) ; |
|
} |
|
} |
|
|
|
// cleanup |
|
if ( date ) CFRelease(date) ; |
|
if ( o ) free((void*) o) ; |
|
if ( d ) free((void*) d) ; |
|
|
|
*pbOK = bOK ; |
|
return bOK ; |
|
} |
|
|
|
/* ----------------------------------------------------------- */ |
|
/* main - main function of the program of course */ |
|
/* ----------------------------------------------------------- */ |
|
int main (int argc, char * const argv[]) |
|
{ |
|
if ( argc < 2 ) return syntax(true) ; |
|
|
|
// parse the command line |
|
Option aOptions[lengthof(titles)] ; |
|
bool bOK = true ; // argument is good |
|
bool bOption = true ; // still processing options |
|
int nFile = 0 ; // first file |
|
|
|
// parse the -option[=date] string and find aFile |
|
for ( int a = 1 ; bOK && a < argc ; a++ ) |
|
{ |
|
const char* arg = argv[a] ; |
|
if ( bOption ) |
|
{ |
|
bOption = parseOption(arg,aOptions,&bOK) ; |
|
} |
|
if ( !bOption && bOK ) |
|
{ |
|
// fixup syntax myTouch filename => myTouch -all filename |
|
if ( a == 1 ) parseOption("-all",aOptions,&bOK) ; |
|
|
|
bOK = fexist(arg) ; |
|
if ( !bOK ) { |
|
fprintf(stderr,"file %s does not exist\n",arg) ; |
|
} |
|
if ( bOK && !nFile ) nFile = a ; |
|
} |
|
} |
|
|
|
// do we have some files? |
|
if ( bOK && !nFile ) { |
|
bOK = false ; |
|
fprintf(stderr,"no files given\n") ; |
|
} |
|
if ( !bOK ) return syntax(false) ; |
|
|
|
// now process the files |
|
OSStatus err = noErr ; |
|
for ( int f = nFile ; !err && f < argc ; f ++ ) |
|
{ |
|
UInt8* path = (UInt8*) argv[f] ; |
|
for ( int option = 1 ; !err && option < lengthof(titles) ; option++ ) |
|
{ |
|
CFDateRef date = aOptions[option].getDate() ; |
|
bool report = aOptions[option].getReport() ; |
|
|
|
if ( !err && date ) err = setDate (path,option,date) ; |
|
if ( !err && report ) err = reportDate (path,option) ; |
|
} |
|
} |
|
|
|
if ( err ) fprintf(stderr,"error %d\n",err) ; |
|
return (int) err ; |
|
} |
|
|
|
/* |
|
----------------------------------------------------------------- |
|
* Revision History |
|
Modified Reason |
|
20071019 First published version |
|
----------------------------------------------------------------- |
|
*/ |