# tko_file.tcl --
#
# Additional file commands.
#
# Copyright (c) 2024- <r.zaumseil@freenet.de>
namespace eval ::tko::file {
namespace export *
namespace ensemble create
}
# ::tko::file::read --
# Return content of given file.
#
# Arguments:
# file - Name of file to read
# args - fconfigure options
proc ::tko::file::read {file args} {
if {[catch {
set myFd [open $file r]
fconfigure $myFd {*}$args
set myRet [::read $myFd]
} m]} {
catch {close $myFd}
return -code error "read $file: $m"
}
close $myFd
return $myRet
}
# ::tko::file::write --
# Write given data into given file
#
# Arguments:
# file - Name of file to write
# data - Data to write into file
# args - fconfigure options
proc ::tko::file::write {file data args} {
if {[catch {
set myDir [file dirname $file]
if {![file isdirectory $myDir]} {
file mkdir $myDir
}
set myFd [open $file w]
fconfigure $myFd {*}$args
puts -nonewline $myFd $data
} m]} {
catch {close $myFd}
return -code error "write $file: $m"
}
close $myFd
return
}
# ::tko::file::tail --
# Implement file tail function.
# If the file disappears or is replaced by a new file with the
# same name it is handled transparently.
#
# The behaviour of this function is based upon tail in the gnu
# fileutils alpha release 4.0 package.
#
# Arguments
# mode - One of start, stop, status. _runis for internal use!
# file - Name of file. Can be file pattern for status command.
# command - Used when mode=start. Name of command for output.
# The command will called with appending "stdout linelist" for
# new data and "stderr {fd readon}" for state changes.
# delay - Used when mode=start. Delay time in ms. Default ist 1000ms
#
# Usage:
# # define receive proc.
# proc cmd {mode arg} {
# if {$mode eq {stdout}} {
# foreach line $arg {
# # do something with tail'd lines
# }
# } else {
# lassign $arg fd reason
# # do something with state message
# }
# }
# # startup
# tko::file::tail start t.txt ::cmd
# ..
# # check running tail's
# tko::file::tail state *
# # stop it
# tko::file::tail stop t.txt
#
proc ::tko::file::tail {mode file {command {}} {delay 1000}} {
array set ::tko::file::tail {};# ensure variable exists
switch -- $mode {
start {;# file command delay
if {$file eq {}} {
return -code error "empty file name"
}
if {[info exists ::tko::file::tail($file)]} {
return -code error "already running on $file"
}
if {$command eq {}} {
return -code error "missing command parameter"
}
set myInode -1
set mySize -1
set myMtime -1
}
stop {;# file
unset -nocomplain ::tko::file::tail($file)
after cancel [list ::tko::file::tail _run $file]
return
}
state {;# pattern
if {$file eq {}} {set file *}
return [array get ::tko::file::tail $file]
}
_run {;# file
if {![info exists ::tko::file::tail($file)]} return
lassign $::tko::file::tail($file) myCommand myDelay myFid myInode mySize myMtime
}
default {
return -code "unknown '$mode', should be one of start, stop, state"
}
}
# if the file exists at this iteration, tail it
if {[::file readable $file]} {
::file stat $file fstat
# if the inode has changed since the last iteration, reopen the file.
# this is from tail v4.0 in the gnu fileutils package.
if {$fstat(ino) != $myInode} {
catch {close $myFid}
{*}$myCommand stderr "$myFid inode"
set myFid {}
} else {
if {$fstat(size) < $mySize} {
if {[catch {seek $myFid 0}]} {
catch {close $myFid}
{*}$myCommand stderr "$myFid size"
set myFid {}
} else {
{*}$myCommand stderr "$myFid seek"
}
}
if {$fstat(size) == $mySize && $fstat(mtime) != $myMtime} {
if {[catch {seek $myFid 0}]} {
catch {close $myFid}
{*}$myCommand stderr "$myFid mtime"
set myFid {}
} else {
{*}$myCommand stderr "$myFid seek"
}
}
}
# if the file is not open, open it!
if {$myFid eq {}} {
if {[catch {set myFid [open $file r]} myMsg]} {
{*}$myCommand notopen=$myMsg
} else {
fconfigure $myFid -blocking off
fconfigure $myFid -buffering line
{*}$myCommand stderr "$myFid open"
}
# normal operation, get new lines
} else {
# set a variable with the content of the new data
set myLines [list]
while {[gets $myFid myLine] >= 0} {
lappend myLines $myLine
}
{*}$myCommand stdout $myLines
}
set ::tko::file::tail($file) [list $myCommand $myDelay $myFid\
$fstat(ino) $fstat(size) $fstat(mtime) $myFid]
# if the file doesn't exist, make sure we aren't creating an NFS orphan.
} else {
# maybe the file got nuked? Handle it!
if {$myFid ne {}} {
catch {close $myFid}
}
set ::tko::file::tail($file) [list $myCommand $myDelay {} -1 -1 -1]
}
# lather, rinse, repeat. This is not recursion!
after $myDelay [list ::tko::file::tail _run $file]
}
# vim: set ts=4 sw=4 sts=4 et :