Worklog

From wikipost
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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