Worklog
Jump to navigation
Jump to search
Worklog
#!/bin/sh # worklog # # (C)2010 Marcel Post. All Rights Reserved # Last modified: 22 December 2010 (optimised for Bashv4, possibly Dash too) # # This script tracks changes made to files in a target directory. # The way it works is that it will calculate the time between when # a file was first edited (poll every 5 minutes) and when the last # change was made (assuming that no edits were made for 60 minutes). # # For permissions, the cron executer must own the 'worklog' file # # Example cron entry: # # */5 * * * * /usr/local/bin/worklog /var/www/attendance [path-to-worklog-file] # # # BUGS # # once had that a file could not be 'stat' by worklog, as if it was locked by # vi when I was writing back the changes or so.. # # # TODO # - add a --help switch with version number etc # # User Variables: WL=worklog # name and location of the worklog file TMPDIR=/tmp # a temp directory that we can safely write to IDLEMINS=20 # when we should consider an edit to be finished DEBUG=0 # turn on for more verbose output # --- script follows --- if test $# -lt 1 then # no arguments provided echo "Syntax: worklog [path-to-worklog-file]" exit 1 fi if test $# -lt 2 then # one argument provided. Can either be: # -show to display the worklog file, or # to find changes in the 'path' files and directories and # write the updates to the worklog file located in 'path' if test $1 = "-show" then # use current directory to find the workdir file DEBUG=2 # set projectdir PROJECTDIR=`pwd` # set worklog file WORKLOG=$PROJECTDIR/$WL else # using project directory as specified with argument 1 # also use the path directory to find the worklog file if test -d $1 then # project dir exists ok PROJECTDIR=$1 # set worklog file WORKLOG=$PROJECTDIR/$WL else echo "Project directory does not exist" exit 1 fi fi else # at least two arguments provided. Can either be: # -show to display the worklog file in the # specified 'path' # update worklog in a possibly different # location than the 'path' specification if test $1 = "-show" then # use second argument to find the worklog file DEBUG=2 # set location of worklog file if test -f $2 then # worklog file exists ok WORKLOG=$2 else echo "Could not access worklog file at $2" echo "Please create a worklog file by running an update first" echo "Syntax: worklog [path-to-worklog-file]" exit 1 fi else # test if arguments link to an existing directory and a worklogfile if test -d $1 then # project dir exists ok PROJECTDIR=$1 else echo "Project directory does not exist" exit 1 fi # just set the name and location of the worklog file to the second argument # testing if it exists happens further in the script WORKLOG=$2 fi fi # uncomment for debugging purposes #if test $# -eq 2 #then # if test $2 = "-d" # then # DEBUG=1 # echo # fi # if test $2 = "-show" # then # # show real dates instead of timestamps # DEBUG=2 # echo # fi #fi # test if project directory exists if ! test -d $PROJECTDIR then echo "Project directory not found" exit 1 fi # test if logfile exists if test -f $WORKLOG then # -- check existing worklog file -- # check if worklog has a filesize greater than zero if test -s $WORKLOG then # check if we can recognise the header of the file WORKLOG_HEADER=`head -n 1 $WORKLOG` if test `echo $WORKLOG_HEADER | awk -F : '{print $1}'` = "LAST-CHANGED" > /dev/null 2>&1 then NEWFILE=0 else echo "The worklog file seems corrupt. Please check." echo "if possible delete $WORKLOG" exit 1 fi else NEWFILE=1 fi # check permissions if ! test -O $WORKLOG then if test $DEBUG != 2 then echo "File $WORKLOG is not owned by the user running this script! Exiting now." exit 1 fi fi # check if we have write permissions to the worklog file # who is running this script RUNUID=`set | grep ^UID | awk -F = '{print $2}'` #get stat from logfile LOGFSTAT=`/usr/bin/stat $WORKLOG | grep Uid` #check if Uid is same as the user running this script LOGF_PERMS=`echo $LOGFSTAT | awk -F Uid '{print $1}' | awk -F / '{print $2}' | cut -c 3` if test "$LOGF_PERMS" != "w" then echo "No write permissions to worklog file! Exiting now." exit 1 fi # Excellent! # if we're still in the running here, we've got: # # - an existing worklog file (maybe zero length) # - owned by the runner of this script # - with write permissions else # -- create new worklog file -- if test $DEBUG = 2 then echo "Please create a worklog file by running an update first" echo "Syntax: worklog [path-to-worklog-file]" exit 1 else echo "Worklog at $WORKLOG not found" echo -n "Attempting to create a new worklog file... " if touch $WORKLOG > /dev/null 2>&1 then echo "ok" if test $DEBUG = 1 then echo "new worklog created" fi NEWFILE=1 else echo "Permission denied!" exit 1 fi fi fi # -- part 1 b -- # show human readable dates and summarise time spent # if we were called with the -show argument now is the # time to display the worklog file using real date (ymd) # instead of timestamps. if test $DEBUG = 2 then echo "worklog for $1" TOTALTIME=0 TOTALTIME_W=0 TOTALTIME_C=0 MAXLINES=`grep -c minutes $WORKLOG` MAXLINES=$((MAXLINES + 2)) LINENR=3 PREVWEEK=0 DO_WEEKSUMMARY=0 while test $LINENR -le $MAXLINES do READLN=`cat $WORKLOG | head -n $LINENR | tail -n 1` # only process completed entries.. if echo $READLN | grep minutes > /dev/null 2>&1 then DO_PRINT=1 BEGINTIME=`echo $READLN | awk '{print $1}'` WEEKNR=`date -d @$BEGINTIME +%U` if test $PREVWEEK -eq 0 then PREVWEEK=$WEEKNR else if test $WEEKNR != $PREVWEEK then DO_WEEKSUMMARY=1 fi fi else # incomplete line detected. Don't print anything DO_PRINT=0 fi # show week summary line if test $DO_WEEKSUMMARY -eq 1 then if test $TOTALTIME -gt 0 then # new week detected TOTAL_HOURS_W=$((TOTALTIME_W / 60)) TOTAL_HXM_W=$((TOTAL_HOURS_W * 60)) TOTAL_MINUTES_W=$((TOTALTIME_W - TOTAL_HXM_W)) echo -n "Summary for week $PREVWEEK: $TOTALTIME_W minutes $TOTAL_HOURS_W h $TOTAL_MINUTES_W m" # cumulative week totals TOTALTIME_C=$((TOTALTIME_C + TOTALTIME_W)) TOTAL_HOURS_C=$((TOTALTIME_C / 60)) TOTAL_HXM_C=$((TOTAL_HOURS_C * 60)) TOTAL_MINUTES_C=$((TOTALTIME_C - TOTAL_HXM_C)) echo " ($TOTAL_HOURS_C h $TOTAL_MINUTES_C m)" echo "--" PREVWEEK=$WEEKNR fi DO_WEEKSUMMARY=0 TOTALTIME_W=0 fi BEGINTIME=`date -d @$BEGINTIME` ENDTIME=`echo $READLN | awk '{print $2}'` ENDTIME=`date -d @$ENDTIME` DIFFERENCE=`echo $READLN | awk '{print $3}'` TOTALTIME_W=$((TOTALTIME_W + DIFFERENCE)) TOTALTIME=$((TOTALTIME + DIFFERENCE)) # print entry on screen echo "$BEGINTIME to $ENDTIME = $DIFFERENCE minutes" # show week summary if we've reached the last line if test $LINENR -eq $MAXLINES then TOTAL_HOURS_W=$((TOTALTIME_W / 60)) TOTAL_HXM_W=$((TOTAL_HOURS_W * 60)) TOTAL_MINUTES_W=$((TOTALTIME_W - TOTAL_HXM_W)) echo -n "Summary for week $PREVWEEK: $TOTALTIME_W minutes $TOTAL_HOURS_W h $TOTAL_MINUTES_W m" # cumulative week totals TOTALTIME_C=$((TOTALTIME_C + TOTALTIME_W)) TOTAL_HOURS_C=$((TOTALTIME_C / 60)) TOTAL_HXM_C=$((TOTAL_HOURS_C * 60)) TOTAL_MINUTES_C=$((TOTALTIME_C - TOTAL_HXM_C)) if test $TOTALTIME_C -eq $TOTALTIME_W then echo else echo " ($TOTAL_HOURS_C h $TOTAL_MINUTES_C m)" fi echo "--" fi LINENR=$((LINENR + 1)) done echo "---------------------------" echo -n " $TOTALTIME minutes" TOTAL_HOURS=$((TOTALTIME / 60)) TOTAL_HXM=$((TOTAL_HOURS * 60)) TOTAL_MINUTES=$((TOTALTIME - TOTAL_HXM)) echo " $TOTAL_HOURS h $TOTAL_MINUTES m" exit 1 fi # -- part 2 -- # track changes # find out which file was lastly changed # the first line of the worklog file will be used to track changes # # Example: # LAST-CHANGED : 1273572131 left.php # get just the file name of a regular file that was changed last LC_FILENAME=`/bin/ls -tr1 $PROJECTDIR | grep -v $WL | tail -n 1` # get the timestamp for this file LC_TIMESTAMP=`/usr/bin/stat --format=%Y $PROJECTDIR/$LC_FILENAME` # compare this with what's in the worklog # -- create worklog header in case of new file -- if test $NEWFILE = 1 then WORKLOG_HEADER="LAST-CHANGED : $LC_TIMESTAMP $LC_FILENAME" echo $WORKLOG_HEADER > $WORKLOG echo "--" >> $WORKLOG if test $DEBUG = 1 then echo "created new worklog header for file: $LC_FILENAME" fi fi # update the header if a change has been detected if echo $WORKLOG_HEADER | grep -e "$LC_TIMESTAMP $LC_FILENAME" > /dev/null 2>&1 then # no changes detected CHANGES=0 if test $DEBUG = 1 then echo "no changes detected" fi else CHANGES=1 # a recent change has been made if test $DEBUG = 1 then echo "changes detected in file: $LC_FILENAME" echo "updating header" fi # update the worklog header # get the total number of lines in the worklog (minus one) WL_MAXLINES=`wc -l $WORKLOG | awk '{print $1}'` WL_MAXLINES=$((WL_MAXLINES - 1)) #set the new header WORKLOG_HEADER="LAST-CHANGED : $LC_TIMESTAMP $LC_FILENAME" #rewrite the worklog file cat $WORKLOG | tail -n $WL_MAXLINES > $TMPDIR/tmpfil_worklog.001 echo $WORKLOG_HEADER > $WORKLOG cat $TMPDIR/tmpfil_worklog.001 >> $WORKLOG rm $TMPDIR/tmpfil_worklog.001 fi # -- lastline updating -- # get the last line of the worklog WL_LASTLINE=`tail -n 1 $WORKLOG` WL_LL_LENGTH=${#WL_LASTLINE} # the length of the line is an indication as to what's written in it # length up to 18 chars = unknown # length is greater than 30 chars = minutes have been calculated if test $CHANGES = 1 then if test $WL_LL_LENGTH = 2 || test $WL_LL_LENGTH -gt 18 then # open new lastline WL_LASTLINE="$LC_TIMESTAMP unknown" echo $WL_LASTLINE >> $WORKLOG if test $DEBUG = 1 then echo "opening new tracker line" fi fi fi # -- timestamp updating -- # are we checking after the idle time? # get timestamp for right now TIME_NOW=`date +%s` # create timestamp for LC_TIMESTAMP + ( N x 60 ) IDLESECS=$((IDLEMINS * 60)) TIMESTAMP_IDLE=$((LC_TIMESTAMP + IDLESECS)) # test if our present time is the IDLE time past the Last Changed time if test $TIME_NOW -gt $TIMESTAMP_IDLE then # we're past our idle time # update 'unknown' with the last known time from the header # and calculate the difference # # just exit if unkown is not the last keyword if test $WL_LL_LENGTH = 18 then # line has not been closed yet, do so now if test $DEBUG = 1 then echo "past idle time, closing tracker line" fi # get the last known change time from the header WL_TS=`head -n 1 $WORKLOG | awk '{print $3}'` #rewrite the last line of the logfile # get the total number of lines in the worklog (minus one) WL_MAXLINES=`wc -l $WORKLOG | awk '{print $1}'` WL_MAXLINES=$((WL_MAXLINES - 1)) #set the lastline with an updated timestamp WL_LASTLINE_PRE=`echo "$WL_LASTLINE" | awk '{print $1}'` #calculate the time between start and finish WL_STARTTIME=`tail -n 1 $WORKLOG | awk '{print $1}'` TIMESPAN=$((WL_TS - WL_STARTTIME)) TIMESPAN=$((TIMESPAN / 60)) WL_LASTLINE="$WL_LASTLINE_PRE $WL_TS $TIMESPAN minutes" #rewrite the worklog file cat $WORKLOG | head -n $WL_MAXLINES > $TMPDIR/tmpfil_worklog.001 cat $TMPDIR/tmpfil_worklog.001 > $WORKLOG if [ $TIMESPAN -le 0 ] || [ $TIMESPAN -gt 720 ] then # ignore updating the last line, it was probably faulty anyway echo > /dev/null if test $DEBUG = 1 then echo "timespan zero, negative or too big, not writing tracker line" echo "details:" echo "original starttime: $WL_STARTTIME" echo "last changed time: $WL_TS" fi else echo $WL_LASTLINE >> $WORKLOG if test $DEBUG = 1 then echo "updating tracker line with timespan" fi fi rm $TMPDIR/tmpfil_worklog.001 fi fi if test $DEBUG = 1 then echo "Timestamp now: `date +%s` `date`" fi # That's all folks