You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

161 lines
4.8 KiB

# Handling of read-only channels.
#
# (c) 2010 Wojciech Kocjan, Pawel Salawa
# (c) 2011-2014 Wojciech Kocjan
# (c) 2024 Konstantin Kushnir
namespace eval cookfs {}
namespace eval cookfs::tcl {}
namespace eval cookfs::tcl::readerchannel {}
# creates a new channel for reading data from a VFS
# uses reflected channels
proc cookfs::tcl::readerchannel {fsid chunklist} {
variable dirlistParameters
upvar #0 $fsid fs
set id [incr fs(channelId)]
set chid "$fsid.ch$id"
upvar #0 $chid ch
array set ch {
offset 0
firstread 1
}
# initialize internal information with chunk list
set ch(chunkinfo) [list]
set offset 0
foreach {chunkId chunkOffset chunkSize} $chunklist {
lappend ch(chunkinfo) $offset $chunkId $chunkOffset $chunkSize
incr offset $chunkSize
}
set ch(filesize) $offset
# create Tcl channel
if {[info commands ::chan] != ""} {
set ch(refchannel) \
[::chan create {read} [list cookfs::tcl::readerchannel::handler $fsid $chid]]
} else {
# fallback to rechan
set ch(refchannel) \
[rechan [list cookfs::tcl::readerchannel::handler $fsid $chid] 2]
}
fconfigure $ch(refchannel) -buffersize 65536
return $ch(refchannel)
}
# event handling for watch subcommand
proc cookfs::tcl::readerchannel::eventClean {fd} {
variable eventEnable
eventSet $fd 0
}
proc cookfs::tcl::readerchannel::eventWatch {fd a} {
if {[lindex $a 0] == "read"} {
eventSet $fd 1
} else {
eventSet $fd 0
}
}
proc cookfs::tcl::readerchannel::eventSet {fd e} {
variable eventEnable
set cmd [list ::cookfs::tcl::readerchannel::eventPost $fd]
after cancel $cmd
if {$e} {
set eventEnable($fd) 1
after 0 $cmd
} else {
catch {unset eventEnable($fd)}
}
}
proc cookfs::tcl::readerchannel::eventPost {fd} {
variable eventEnable
if {[info exists eventEnable($fd)] && $eventEnable($fd)} {
chan postevent $fd read
}
}
# handle command for a channel
proc cookfs::tcl::readerchannel::handler {fsid chid command args} {
upvar #0 $fsid fs
upvar #0 $chid ch
switch -- $command {
initialize {
return [list initialize finalize watch read seek]
}
finalize - close {
eventClean [lindex $args 0]
unset $chid
}
watch {
eventWatch [lindex $args 0] [lindex $args 1]
}
read {
# read data from a channel
foreach {channelId bytesleft} $args break
set rc ""
set currentoffset $ch(offset)
# iterate over chunks and read part of the data that matches our current offset
foreach {offset chunkId chunkOffset chunkSize} $ch(chunkinfo) {
if {($offset <= $currentoffset) && (($offset + $chunkSize) > $currentoffset)} {
set doffset [expr {$currentoffset - $offset}]
set cpoffset [expr {$chunkOffset + $doffset}]
set dsize [expr {$chunkSize - $doffset}]
if {$dsize > $bytesleft} {
set dsize $bytesleft
}
if {$chunkId < 0} {
error "Unable to handle small files not written yet!"
}
if { $ch(firstread) } {
if { ![$fs(pages) getcache $chunkId] } {
$fs(pages) ticktock
}
set ch(firstread) 0
}
set weight [expr { [$fs(index) getblockusage $chunkId] > 1 ? 1 : 0 }]
set d [$fs(pages) get -weight $weight $chunkId]
if {($cpoffset != 0) || ($dsize != [string length $d])} {
set d [string range $d $cpoffset [expr {$cpoffset + $dsize - 1}]]
}
append rc $d
incr bytesleft -[string length $d]
incr currentoffset [string length $d]
}
}
set ch(offset) $currentoffset
return $rc
}
seek {
# seek within the file
foreach {channelId offset base} $args break
switch -- $base {
start - 0 {
set ch(offset) $offset
}
current - 1 {
incr ch(offset) $offset
}
end - 2 {
set ch(offset) [expr {$ch(filesize) + $offset}]
}
}
if {$ch(offset) < 0} {set ch(offset) 0}
if {$ch(offset) > $ch(filesize)} {set ch(offset) $ch(filesize)}
return $ch(offset)
}
default {
error "Command not implemented"
}
}
}
package provide cookfs::tcl::readerchannel 1.9.0