Worklog

From wikipost
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