Commit 14da75a7 authored by Frank Bergmann's avatar Frank Bergmann

- Upgrade XoTCL Core to 0.124

parent f8ad3bf0
...@@ -13,6 +13,7 @@ package require xotcl::serializer ...@@ -13,6 +13,7 @@ package require xotcl::serializer
::xotcl::Object instproc serialize ::xotcl::Object instproc serialize
::xotcl::Object instproc show-object ::xotcl::Object instproc show-object
::xotcl::Object instforward db_1row ::xotcl::Object instforward db_1row
::xotcl::Object instforward db_0or1row
::xotcl::Object instproc destroy_on_cleanup ::xotcl::Object instproc destroy_on_cleanup
::xotcl::Object instproc set_instance_vars_defaults ::xotcl::Object instproc set_instance_vars_defaults
::xotcl::nonposArgs proc integer ::xotcl::nonposArgs proc integer
...@@ -70,19 +71,22 @@ if {$::xotcl::version < 1.5} { ...@@ -70,19 +71,22 @@ if {$::xotcl::version < 1.5} {
if {[info command ::nx::Object] ne ""} { if {[info command ::nx::Object] ne ""} {
ns_log notice "Defining minimal XOTcl 1 compatibility" ns_log notice "Defining minimal XOTcl 1 compatibility"
::nsf::alias ::xo::Attribute instvar ::nsf::methods::object::instvar ::nsf::method::alias ::xo::Attribute instvar ::nsf::methods::object::instvar
# the following line would cause a dependency of an nx object to xotcl (serializer) # The following line would cause a dependency of an nx object to
#::nsf::alias ::nx::Slot istype ::nsf::classes::xotcl::Object::istype # xotcl (serializer); since XOTcl depends on NX, this would be a
::nx::Slot public method istype {class} { # cyclic dependency.
return [expr {[::nsf::is class $class] && # ::nsf::method::alias ::nx::Slot istype ::nsf::classes::xotcl::Object::istype
[::nsf::dispatch [self] ::nsf::methods::object::info::hastype $class]}] # Therefore, we just grab the body to reduce dependencies on nsf internals
} ::nx::Slot public method istype {class} [::nx::Object info method body ::nsf::classes::xotcl::Object::istype]
::nx::Slot public alias set -frame object ::set ::nx::Slot public alias set -frame object ::set
::nx::Slot public method exists {var} {::nsf::existsvar [self] $var} ::nx::Slot public method exists {var} {::nsf::var::exists [self] $var}
::nx::Object public method serialize {} {::Serializer deepSerialize [self]} ::nx::Object public method serialize {} {::Serializer deepSerialize [self]}
::nx::Object method set_instance_vars_defaults {} {:configure} ::nx::Object method set_instance_vars_defaults {} {:configure}
::nx::Object public method destroy_on_cleanup {} {set ::xo::cleanup([self]) [list [self] destroy]} ::nx::Object public method destroy_on_cleanup {} {set ::xo::cleanup([self]) [list [self] destroy]}
::nx::Object method qn {query_name} {
return "dbqd.[:uplevel [list current class]]-[:uplevel [list current method]].$query_name"
}
::xotcl::Object instproc set_instance_vars_defaults {} {:configure} ::xotcl::Object instproc set_instance_vars_defaults {} {:configure}
::xotcl::Object proc setExitHandler {code} {::nsf::exithandler set $code} ::xotcl::Object proc setExitHandler {code} {::nsf::exithandler set $code}
...@@ -91,6 +95,7 @@ if {[info command ::nx::Object] ne ""} { ...@@ -91,6 +95,7 @@ if {[info command ::nx::Object] ne ""} {
::nx::Object method show-object ::nx::Object method show-object
::nx::Object method set_instance_vars_defaults ::nx::Object method set_instance_vars_defaults
::nx::Object method destroy_on_cleanup ::nx::Object method destroy_on_cleanup
::nx::Object method qn
::nx::Slot method istype ::nx::Slot method istype
::nx::Slot method exists ::nx::Slot method exists
::nx::Slot method set ::nx::Slot method set
...@@ -128,6 +133,7 @@ namespace eval ::xo { ...@@ -128,6 +133,7 @@ namespace eval ::xo {
} }
::xotcl::Object instforward db_1row -objscope ::xotcl::Object instforward db_1row -objscope
::xotcl::Object instforward db_0or1row -objscope
::xotcl::Object instproc serialize {} { ::xotcl::Object instproc serialize {} {
::Serializer deepSerialize [self] ::Serializer deepSerialize [self]
...@@ -245,7 +251,7 @@ proc ::! args { ...@@ -245,7 +251,7 @@ proc ::! args {
} }
::xotcl::Object instproc qn query_name { ::xotcl::Object instproc qn query_name {
set qn "dbqd.[my uplevel self class]-[my uplevel self proc].$query_name" set qn "dbqd.[my uplevel [list self class]]-[my uplevel [list self proc]].$query_name"
return $qn return $qn
} }
namespace eval ::xo { namespace eval ::xo {
...@@ -382,7 +388,7 @@ namespace eval ::xo { ...@@ -382,7 +388,7 @@ namespace eval ::xo {
} else { } else {
# register only once # register only once
if {[lsearch $registered ::xo::cleanup] == -1} { if {[lsearch $registered ::xo::freeconn] == -1} {
ns_ictl trace freeconn ::xo::freeconn ns_ictl trace freeconn ::xo::freeconn
} }
if {[lsearch [ns_ictl gettraces delete] ::xo::at_delete] == -1} { if {[lsearch [ns_ictl gettraces delete] ::xo::at_delete] == -1} {
...@@ -414,6 +420,7 @@ namespace eval ::xo { ...@@ -414,6 +420,7 @@ namespace eval ::xo {
} }
proc at_cleanup {args} { proc at_cleanup {args} {
::xo::broadcast receive
#ns_log notice "*** start of cleanup <$args> ([array get ::xo::cleanup])" #ns_log notice "*** start of cleanup <$args> ([array get ::xo::cleanup])"
set at_end "" set at_end ""
foreach {name cmd} [array get ::xo::cleanup] { foreach {name cmd} [array get ::xo::cleanup] {
...@@ -474,6 +481,7 @@ namespace eval ::xo { ...@@ -474,6 +481,7 @@ namespace eval ::xo {
# problem will not occur. # problem will not occur.
# #
ns_log notice "ON DELETE $args" ns_log notice "ON DELETE $args"
::xo::broadcast clear
set t0 [clock clicks -milliseconds] set t0 [clock clicks -milliseconds]
# #
# Check, if we have a new XOTcl implementation with ::xotcl::finalize # Check, if we have a new XOTcl implementation with ::xotcl::finalize
...@@ -625,6 +633,43 @@ namespace eval ::xo { ...@@ -625,6 +633,43 @@ namespace eval ::xo {
} }
} }
namespace eval ::xo {
#
# xo::broadcast implements a simple mechanism to send commands to
# different connection and scheduled threads. The receiving threads
# have to call "xo::broadcast receive" when they are able to process
# the commands. The connection threads realize this in xo::atcleanup
# after a request was processed (defined in this file).
#
::xotcl::Object create ::xo::broadcast
::xo::broadcast proc send {cmd} {
foreach thread_info [ns_info threads] {
switch -glob -- [lindex $thread_info 0] {
-conn:* -
-sched:* {
set tid [lindex $thread_info 2]
nsv_lappend broadcast $tid $cmd
}
}
}
}
::xo::broadcast proc clear {} {
catch {nsv_unset broadcast [ns_thread id]}
}
::xo::broadcast proc receive {} {
set tid [ns_thread id]
if {[nsv_exists broadcast $tid]} {
foreach cmd [nsv_get broadcast $tid] {
ns_log notice "broadcast received {$cmd}"
if {[catch $cmd errorMsg]} {
ns_log notice "broadcast receive error: $errorMsg for cmd $cmd"
}
}
my clear
}
}
}
#ns_log notice "*** FREECONN? [ns_ictl gettraces freeconn]" #ns_log notice "*** FREECONN? [ns_ictl gettraces freeconn]"
#ns_ictl trace freeconn {ns_log notice "*** FREECONN isconnected=[ns_conn isconnected]"} #ns_ictl trace freeconn {ns_log notice "*** FREECONN isconnected=[ns_conn isconnected]"}
#ns_ictl oncleanup {ns_log notice "*** ONCLEANUP isconnected=[ns_conn isconnected]"} #ns_ictl oncleanup {ns_log notice "*** ONCLEANUP isconnected=[ns_conn isconnected]"}
...@@ -75,7 +75,7 @@ ad_library { ...@@ -75,7 +75,7 @@ ad_library {
if {$script eq "" && [info exists ::xotcl::currentScript]} { if {$script eq "" && [info exists ::xotcl::currentScript]} {
set script $::xotcl::currentScript set script $::xotcl::currentScript
} }
set root_dir [nsv_get acs_properties root_directory] set root_dir [acs_root_dir]
set root_length [string length $root_dir] set root_length [string length $root_dir]
if { $root_dir eq [string range $script 0 [expr {$root_length - 1}]]} { if { $root_dir eq [string range $script 0 [expr {$root_length - 1}]]} {
set script [string range $script [expr {$root_length + 1}] end] set script [string range $script [expr {$root_length + 1}] end]
......
...@@ -596,6 +596,36 @@ namespace eval ::xo::db { ...@@ -596,6 +596,36 @@ namespace eval ::xo::db {
# } # }
::xo::db::Class proc get_all_package_functions {} { ::xo::db::Class proc get_all_package_functions {} {
#
# Load defintions in one swap fropm function args; only for
# those definitions where we do not have function args, we parse
# the function arg aliases.
#
set definitions [db_list_of_lists [my qn get_all_package_functions0] {
select
args.function,
args.arg_name,
args.arg_default
from acs_function_args args
order by function, arg_seq
}]
set last_function ""
set function_args {}
foreach definition $definitions {
foreach {function arg_name default} $definition break
if {$last_function ne "" && $last_function ne $function} {
set ::xo::db::sql::fnargs($last_function) $function_args
#puts stderr "$last_function [list $function_args]"
set function_args {}
}
lappend function_args [list $arg_name $default]
set last_function $function
}
set ::xo::db::sql::fnargs($last_function) $function_args
#puts stderr "$last_function [list $function_args]"
ns_log notice "loaded [array size ::xo::db::sql::fnargs] definitions from function args"
#ns_log notice "... [lsort [array names ::xo::db::sql::fnargs *__*]]"
# #
# Get all package functions (package name, object name) from PostgreSQL # Get all package functions (package name, object name) from PostgreSQL
# system catalogs. # system catalogs.
...@@ -623,6 +653,12 @@ namespace eval ::xo::db { ...@@ -623,6 +653,12 @@ namespace eval ::xo::db {
# } # }
::xo::db::Class instproc get_function_args {package_name object_name} { ::xo::db::Class instproc get_function_args {package_name object_name} {
set key [string toupper ${package_name}__${object_name}]
if {[info exists ::xo::db::sql::fnargs($key)]} {
return $::xo::db::sql::fnargs($key)
}
# #
# Get function_args for a single sql-function from PostgreSQL # Get function_args for a single sql-function from PostgreSQL
# system catalogs. We retrieve always the longest function for # system catalogs. We retrieve always the longest function for
...@@ -792,8 +828,9 @@ namespace eval ::xo::db { ...@@ -792,8 +828,9 @@ namespace eval ::xo::db {
# is to define the correct default values in the database with # is to define the correct default values in the database with
# define_function_args() # define_function_args()
::xo::db::Class array set defaults { ::xo::db::Class array set fallback_defaults {
"content_item__new" {RELATION_TAG null DESCRIPTION null TEXT null "content_item__new" {
RELATION_TAG null DESCRIPTION null TEXT null
CREATION_IP null NLS_LANGUAGE null LOCALE null CONTEXT_ID null CREATION_IP null NLS_LANGUAGE null LOCALE null CONTEXT_ID null
DATA null TITLE null ITEM_ID null DATA null TITLE null ITEM_ID null
CREATION_DATE now CREATION_DATE now
...@@ -809,14 +846,28 @@ namespace eval ::xo::db { ...@@ -809,14 +846,28 @@ namespace eval ::xo::db {
"content_type__drop_type" { "content_type__drop_type" {
DROP_CHILDREN_P f DROP_TABLE_P f DROP_OBJECTS_P f DROP_CHILDREN_P f DROP_TABLE_P f DROP_OBJECTS_P f
} }
"acs_attribute__create_attribute" {
PRETTY_PLURAL null TABLE_NAME null COLUMN_NAME null
DEFAULT_VALUE null SORT_ORDER null DATABASE_TYPE null SIZE null
REFERENCES null CHECK_EXPR null COLUMN_SPEC null
}
"acs_object_type__create_type" {
TYPE_EXTENSION_TABLE null NAME_METHOD null
}
} }
::xo::db::Class instproc fix_function_args {function_args package_name object_name} { ::xo::db::Class instproc fix_function_args {function_args package_name object_name} {
if {![[self class] exists defaults(${package_name}__$object_name)]} { #
# Load fallback defaults for buggy function args. The values
# provided here are only used for function args without specified
# defaults. This is a transitional solution; actually, the
# function args should be fixed.
#
if {![[self class] exists fallback_defaults(${package_name}__$object_name)]} {
return $function_args return $function_args
} }
array set additional_defaults [[self class] set defaults(${package_name}__$object_name)] array set additional_defaults [[self class] set fallback_defaults(${package_name}__$object_name)]
set result [list] set result [list]
foreach arg $function_args { foreach arg $function_args {
foreach {arg_name default_value} $arg break foreach {arg_name default_value} $arg break
......
...@@ -72,6 +72,7 @@ namespace eval ::xo { ...@@ -72,6 +72,7 @@ namespace eval ::xo {
{-init_url true} {-init_url true}
{-keep_cc false} {-keep_cc false}
{-form_parameter} {-form_parameter}
{-export_vars true}
} { } {
Create the connection context ::xo::cc and a package object Create the connection context ::xo::cc and a package object
if these are none defined yet. The connection context ::xo::cc if these are none defined yet. The connection context ::xo::cc
...@@ -134,7 +135,7 @@ namespace eval ::xo { ...@@ -134,7 +135,7 @@ namespace eval ::xo {
auth::require_login auth::require_login
} }
::xo::cc export_vars -level 2 if {$export_vars} {::xo::cc export_vars -level 2}
return $package_id return $package_id
} }
......
...@@ -171,8 +171,8 @@ namespace eval ::xo::tdom { ...@@ -171,8 +171,8 @@ namespace eval ::xo::tdom {
set HTMLattribute $attribute set HTMLattribute $attribute
} }
#my msg "[my name] check for $attribute => [my exists $attribute]" #my msg "[my name] check for $attribute => [my exists $attribute]"
if {[my uplevel info exists $attribute]} { if {[my uplevel [list info exists $attribute]]} {
lappend pairs $HTMLattribute [my uplevel set $attribute] lappend pairs $HTMLattribute [my uplevel [list set $attribute]]
} }
} }
return $pairs return $pairs
...@@ -196,6 +196,13 @@ namespace eval ::xo::tdom { ...@@ -196,6 +196,13 @@ namespace eval ::xo::tdom {
namespace eval ::xo { namespace eval ::xo {
#
# Escape provided char in provided string with backslash
#
proc backslash_escape {char string} {
return [string map [list $char \\$char] $string]
}
# #
# Localization # Localization
# #
...@@ -424,7 +431,8 @@ namespace eval ::xo { ...@@ -424,7 +431,8 @@ namespace eval ::xo {
#ns_return 200 text/plain $output #ns_return 200 text/plain $output
my instvar name my instvar name
if {![my exists name]} {set name "table"} if {![my exists name]} {set name "table"}
ns_set put [ns_conn outputheaders] Content-Disposition "attachment;filename=$name.csv" set fn [xo::backslash_escape \" $name.csv]
ns_set put [ns_conn outputheaders] Content-Disposition "attachment;filename=\"$fn\""
ns_return 200 text/csv $output ns_return 200 text/csv $output
} }
...@@ -458,7 +466,7 @@ namespace eval ::xo { ...@@ -458,7 +466,7 @@ namespace eval ::xo {
Class Field \ Class Field \
-superclass ::xo::OrderedComposite::Child \ -superclass ::xo::OrderedComposite::Child \
-parameter {label {html {}} {orderby ""} name {richtext false} no_csv {CSSclass ""}} \ -parameter {label {html {}} {orderby ""} name {richtext false} no_csv {CSSclass ""} {hide 0}} \
-instproc init {} { -instproc init {} {
my set name [namespace tail [self]] my set name [namespace tail [self]]
} \ } \
...@@ -472,7 +480,7 @@ namespace eval ::xo { ...@@ -472,7 +480,7 @@ namespace eval ::xo {
Class BulkAction \ Class BulkAction \
-superclass ::xo::OrderedComposite::Child \ -superclass ::xo::OrderedComposite::Child \
-parameter {name id {html {}}} \ -parameter {name id {html {}} {hide 0}} \
-instproc actions {cmd} { -instproc actions {cmd} {
#my init #my init
set grandParent [[my info parent] info parent] set grandParent [[my info parent] info parent]
......
...@@ -61,6 +61,11 @@ namespace eval ::xo { ...@@ -61,6 +61,11 @@ namespace eval ::xo {
} { } {
my instvar uri method urlv destination my instvar uri method urlv destination
ad_conn -reset ad_conn -reset
# Make sure, there is no ::ad_conn(request); otherwise the
# developer support will add all its output to a single var, which
# can lead easily to running out of resources in busy sites. When
# unset, the developer support will create its own id.
catch {unset ::ad_conn(request)}
set uri [ns_urldecode [ns_conn url]] set uri [ns_urldecode [ns_conn url]]
set url_regexp "^[my url]" set url_regexp "^[my url]"
#my log "--conn_setup: uri '$uri' my url='[my url]' con='[ns_conn url]'" #my log "--conn_setup: uri '$uri' my url='[my url]' con='[ns_conn url]'"
...@@ -119,9 +124,57 @@ namespace eval ::xo { ...@@ -119,9 +124,57 @@ namespace eval ::xo {
} { } {
set filter_url [my url]* set filter_url [my url]*
set url [my url]/* set url [my url]/*
#
# Methods defined by RFC 2086 (19.6.1 Additional Request Methods):
#
# LINK UNLINK PATCH
#
# Methods defined by RFC 2616:
#
# OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT
#
# Methods defined by RF C2518:
#
# PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK
#
# Methods defined by RFC 3253 (versioning extensions):
#
# VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT
# MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL
# MKACTIVITY
#
# Methods defined by RFC 3648 (ordered collections):
#
# ORDERPATCH
#
# Methods defined by RFC 3744 (WebDAV):
#
# ACL REPORT
#
# Methods defined by RFC 4437 (redirect reference resources):
#
# MKREDIRECTREF UPDATEREDIRECTREF
#
# Methods defined by RFC $791 (CalDAV):
#
# MKCALENDAR
#
# Methods defined by RFC 4918 (HTTP Extensions):
#
# COPY LOCK MKCOL MOVE PROPFIND PROPPATCH UNLOCK
#
# Methods defined by RFC 5323 (WebDAV SEARCH):
#
# SEARCH
#
# Methods defined by RFC 5789:
#
# PATCH
#
foreach method { foreach method {
GET HEAD PUT POST MKCOL COPY MOVE PROPFIND PROPPATCH GET HEAD PUT POST MKCOL COPY MOVE PROPFIND PROPPATCH
DELETE LOCK UNLOCK OPTIONS DELETE LOCK UNLOCK OPTIONS
REPORT
} { } {
ns_register_filter preauth $method $filter_url [self] ns_register_filter preauth $method $filter_url [self]
ns_register_proc $method $url [self] handle_request ns_register_proc $method $url [self] handle_request
......
...@@ -47,12 +47,39 @@ if {![string match *contentsentlength* $msg]} { ...@@ -47,12 +47,39 @@ if {![string match *contentsentlength* $msg]} {
FileSpooler create fileSpooler FileSpooler create fileSpooler
fileSpooler set tick_interval 60000 ;# 1 min fileSpooler set tick_interval 60000 ;# 1 min
fileSpooler proc spool {{-delete false} -channel -filename -context {-client_data ""}} { fileSpooler proc deliver_ranges {ranges client_data filename fd channel} {
set first_range [lindex $ranges 0]
set remaining_ranges [lrange $ranges 1 end]
foreach {from to size} $first_range break
if {$remaining_ranges eq ""} {
# A single delivery, which is as well the last; when finished
# with this chunk, terminate delivery
set cmd [list [self] end-delivery -client_data $client_data $filename $fd $channel]
} else {
#
# For handling multiple ranges, HTTP/1.1 requires multipart
# messages (multipart media type: multipart/byteranges);
# currenty these are not implemented (missing test cases). The
# code handling the range tag switches currently to full
# delivery, when multiple ranges are requested.
#
set cmd [list [self] deliver_ranges $remaining_ranges $client_data $filename $fd $channel]
}
seek $fd $from
#ns_log notice "Range seek $from $filename // $first_range"
fcopy $fd $channel -size $size -command $cmd
}
fileSpooler proc spool {{-ranges ""} {-delete false} -channel -filename -context {-client_data ""}} {
set fd [open $filename] set fd [open $filename]
fconfigure $fd -translation binary fconfigure $fd -translation binary
fconfigure $channel -translation binary fconfigure $channel -translation binary
if {$ranges eq ""} {
ns_log notice "no Range spool for $filename"
fcopy $fd $channel -command [list [self] end-delivery -client_data $client_data $filename $fd $channel]
} else {
my deliver_ranges $ranges $client_data $filename $fd $channel
}
#ns_log notice "--- start of delivery of $filename (running:[array size ::running])" #ns_log notice "--- start of delivery of $filename (running:[array size ::running])"
fcopy $fd $channel -command [list [self] end-delivery -client_data $client_data $filename $fd $channel]
set key $channel,$fd,$filename set key $channel,$fd,$filename
set ::running($key) $context set ::running($key) $context
if {$delete} {set ::delete_file($key) 1} if {$delete} {set ::delete_file($key) 1}
...@@ -91,6 +118,9 @@ if {![string match *contentsentlength* $msg]} { ...@@ -91,6 +118,9 @@ if {![string match *contentsentlength* $msg]} {
fileSpooler tick fileSpooler tick
###############
# h264Spooler
###############
# #
# A first draft of a h264 pseudo streaming spooler. # A first draft of a h264 pseudo streaming spooler.
# Like for the fileSpooler, we create a single spooler object # Like for the fileSpooler, we create a single spooler object
...@@ -167,6 +197,63 @@ if {![string match *contentsentlength* $msg]} { ...@@ -167,6 +197,63 @@ if {![string match *contentsentlength* $msg]} {
} }
} }
#################
# AsyncDiskWriter
#################
::xotcl::Class create ::AsyncDiskWriter -parameter {
{blocksize 4096}
{autoflush false}
{verbose false}
}
::AsyncDiskWriter instproc log {msg} {
if {[my verbose]} {ns_log notice "[self] --- $msg"}
}
::AsyncDiskWriter instproc open {-filename {-mode w}} {
my set channel [open $filename $mode]
my set content ""
my set filename $filename
fconfigure [my set channel] -translation binary -blocking false
my log "open [my set filename]"
}
::AsyncDiskWriter instproc close {{-sync false}} {
my instvar content channel
if {$sync || [string length $content] == 0} {
my log "close sync"
if {$content ne ""} {
fconfigure $channel -translation binary -blocking true
puts -nonewline $channel $content
}
close $channel
my destroy
} else {
my log "close async"
my set finishWhenDone 1
}
}
::AsyncDiskWriter instproc async_write {block} {
my append content $block
fileevent [my set channel] writable [list [self] writeBlock]
}
::AsyncDiskWriter instproc writeBlock {} {
my instvar content blocksize channel
if {[string length $content] < $blocksize} {
puts -nonewline $channel $content
my log "write [string length $content] bytes"
fileevent [my set channel] writable ""
set content ""
if {[my autoflush]} {flush $channel}
if {[my exists finishWhenDone]} {
my close -sync true
}
} else {
set chunk [string range $content 0 [expr {$blocksize-1}]]
set content [string range $content $blocksize end]
puts -nonewline $channel $chunk
my log "write [string length $chunk] bytes ([string length $content] buffered)"
}
}
############### ###############
# Subscriptions # Subscriptions
...@@ -225,6 +312,11 @@ if {![string match *contentsentlength* $msg]} { ...@@ -225,6 +312,11 @@ if {![string match *contentsentlength* $msg]} {
incr ::subscription_count incr ::subscription_count
} }
###############
# HttpSpooler
###############
Class ::HttpSpooler -parameter {channel {timeout 10000} {counter 0}} Class ::HttpSpooler -parameter {channel {timeout 10000} {counter 0}}
::HttpSpooler instproc init {} { ::HttpSpooler instproc init {} {
my set running 0 my set running 0
...@@ -297,6 +389,19 @@ if {![string match *contentsentlength* $msg]} { ...@@ -297,6 +389,19 @@ if {![string match *contentsentlength* $msg]} {
-timeout [my timeout] -post_data $post_data -request_manager [self] -timeout [my timeout] -post_data $post_data -request_manager [self]
} }
} }
#
# Add an exit handler to close all AsyncDiskWriter, when this thread goes
# down.
#
::xotcl::Object setExitHandler {
ns_log notice "--- exit handler"
foreach writer [::AsyncDiskWriter info instances -closure] {
ns_log notice "close AsyncDiskWriter $writer"
$writer close
}
}
} -persistent 1 ;# -lightweight 1 } -persistent 1 ;# -lightweight 1
bgdelivery ad_forward running { bgdelivery ad_forward running {
...@@ -340,7 +445,8 @@ bgdelivery ad_proc returnfile { ...@@ -340,7 +445,8 @@ bgdelivery ad_proc returnfile {
&& [info command h264open] ne ""}] && [info command h264open] ne ""}]
if {[info exists content_disposition]} { if {[info exists content_disposition]} {
ns_set put [ns_conn outputheaders] Content-Disposition "attachment;filename=$content_disposition" set fn [xo::backslash_escape \" $content_disposition]
ns_set put [ns_conn outputheaders] Content-Disposition "attachment;filename=\"$fn\""
} }
if {$use_h264} { if {$use_h264} {
...@@ -371,18 +477,58 @@ bgdelivery ad_proc returnfile { ...@@ -371,18 +477,58 @@ bgdelivery ad_proc returnfile {
ns_conn keepalive 0 ns_conn keepalive 0
} }
set range [ns_set iget [ns_conn headers] range]
ns_log notice "Range: '$range' (raw header field)"
if {[regexp {bytes=(.*)$} $range _ range]} {
set ranges [list]
set bytes 0
set pos 0
foreach r [split $range ,] {
regexp {^(\d*)-(\d*)$} $r _ from to
if {$from eq ""} {
# The last $to bytes, $to must be specified; 'to' is
# differently interpreted as in the case, where from is
# non-empty
set from [expr {$size - $to}]
} else {
if {$to eq ""} {set to [expr {$size-1}]}
}
set rangeSize [expr {1 + $to - $from}]
lappend ranges [list $from $to $rangeSize]
set pos [expr {$to + 1}]
incr bytes $rangeSize
}
} else {
set ranges ""
set bytes $size
}
#ns_log notice "Range=$range bytes=$bytes // $ranges"
# #
# For the time being, we write the headers in a simplified version # For the time being, we write the headers in a simplified version
# directly in the spooling thread to avoid the overhead of double # directly in the spooling thread to avoid the overhead of double
# h264opens. # h264opens.
if {!$use_h264} { if {!$use_h264} {
my write_headers $status_code $mime_type $size if {[llength $ranges] == 1 && $status_code == 200} {
set first_range [lindex $ranges 0]
foreach {from to .} $first_range break
ns_set put [ns_conn outputheaders] Content-Range "bytes $from-$to/$size"
ns_log notice "added header-field Content-Range: bytes $from-$to/$size // $ranges"
set status_code 206
} elseif {[llength $ranges]>1} {
ns_log warning "Multiple ranges are currently not supported, ignoring range request"
}
my write_headers $status_code $mime_type $bytes
} }
if {$size == 0} { if {$bytes == 0} {
# Tcl behaves different, when one tries to send 0 bytes via # Tcl behaves different, when one tries to send 0 bytes via
# file_copy. So, we handle this special case here... # file_copy. So, we handle this special case here...
# There is actualy nothing to deliver.... # There is actualy nothing to deliver....
ns_set put [ns_conn outputheaders] "Content-Length" 0
ns_return 200 text/plain {}
return return
} }
...@@ -429,7 +575,7 @@ bgdelivery ad_proc returnfile { ...@@ -429,7 +575,7 @@ bgdelivery ad_proc returnfile {
-client_data $client_data -client_data $client_data
} else { } else {
#my log "FILE SPOOL $filename" #my log "FILE SPOOL $filename"
my do -async ::fileSpooler spool -delete $delete -channel $ch -filename $filename \ my do -async ::fileSpooler spool -ranges $ranges -delete $delete -channel $ch -filename $filename \
-context [list [::xo::cc requestor],[::xo::cc url] [ns_conn start]] \ -context [list [::xo::cc requestor],[::xo::cc url] [ns_conn start]] \
-client_data $client_data -client_data $client_data
} }
......
...@@ -46,7 +46,9 @@ namespace eval ::xo { ...@@ -46,7 +46,9 @@ namespace eval ::xo {
set ([lindex [split [lindex $v 0] :] 0]) 1 set ([lindex [split [lindex $v 0] :] 0]) 1
} }
if {$actual_query eq " "} { if {$actual_query eq " "} {
set actual_query [ns_conn query] if {[ns_conn isconnected]} {
set actual_query [ns_conn query]
}
#my log "--CONN ns_conn query = <$actual_query>" #my log "--CONN ns_conn query = <$actual_query>"
} }
...@@ -134,8 +136,8 @@ namespace eval ::xo { ...@@ -134,8 +136,8 @@ namespace eval ::xo {
my instvar queryparm package_id my instvar queryparm package_id
foreach p [my array names queryparm] { foreach p [my array names queryparm] {
set value [my set queryparm($p)] regsub -all : $p _ varName
uplevel $level [list set $p [my set queryparm($p)]] uplevel $level [list set $varName [my set queryparm($p)]]
} }
uplevel $level [list set package_id $package_id] uplevel $level [list set package_id $package_id]
#::xo::show_stack #::xo::show_stack
...@@ -176,6 +178,7 @@ namespace eval ::xo { ...@@ -176,6 +178,7 @@ namespace eval ::xo {
requestor requestor
user user
url url
mobile
} }
ConnectionContext proc require_package_id_from_url {{-package_id 0} url} { ConnectionContext proc require_package_id_from_url {{-package_id 0} url} {
...@@ -263,6 +266,14 @@ namespace eval ::xo { ...@@ -263,6 +266,14 @@ namespace eval ::xo {
::xo::cc set_user_id $user_id ::xo::cc set_user_id $user_id
::xo::cc process_query_parameter ::xo::cc process_query_parameter
} }
# simple mobile detection
::xo::cc mobile 0
if {[ns_conn isconnected]} {
set user_agent [string tolower [ns_set get [ns_conn headers] User-Agent]]
::xo::cc mobile [regexp (android|webos|iphone|ipad) $user_agent]
}
if {![info exists ::ad_conn(charset)]} { if {![info exists ::ad_conn(charset)]} {
set ::ad_conn(charset) [lang::util::charset_for_locale $locale] set ::ad_conn(charset) [lang::util::charset_for_locale $locale]
set ::ad_conn(language) [::xo::cc lang] set ::ad_conn(language) [::xo::cc lang]
...@@ -450,7 +461,8 @@ namespace eval ::xo { ...@@ -450,7 +461,8 @@ namespace eval ::xo {
ConnectionContext instproc load_form_parameter {} { ConnectionContext instproc load_form_parameter {} {
my instvar form_parameter my instvar form_parameter
if {[ns_conn isconnected]} {
if {[ns_conn isconnected] && [ns_conn method] eq "POST"} {
#array set form_parameter [ns_set array [ns_getform]] #array set form_parameter [ns_set array [ns_getform]]
foreach {att value} [ns_set array [ns_getform]] { foreach {att value} [ns_set array [ns_getform]] {
# For some unknown reasons, Safari 3.* returns sometimes # For some unknown reasons, Safari 3.* returns sometimes
...@@ -465,6 +477,7 @@ namespace eval ::xo { ...@@ -465,6 +477,7 @@ namespace eval ::xo {
array set form_parameter {} array set form_parameter {}
} }
} }
ConnectionContext instproc form_parameter {name {default ""}} { ConnectionContext instproc form_parameter {name {default ""}} {
my instvar form_parameter form_parameter_multiple my instvar form_parameter form_parameter_multiple
if {![info exists form_parameter]} { if {![info exists form_parameter]} {
...@@ -532,7 +545,7 @@ namespace eval ::xo { ...@@ -532,7 +545,7 @@ namespace eval ::xo {
set query [ns_urlencode $var]=[ns_urlencode $value] set query [ns_urlencode $var]=[ns_urlencode $value]
foreach pair [split $old_query &] { foreach pair [split $old_query &] {
foreach {key value} [split $pair =] break foreach {key value} [split $pair =] break
if {$key eq $var} continue if {[ns_urldecode $key] eq $var} continue
append query &$pair append query &$pair
} }
return $query return $query
......
...@@ -157,7 +157,10 @@ namespace eval ::xo::db { ...@@ -157,7 +157,10 @@ namespace eval ::xo::db {
} }
CrClass instproc unknown { obj args } { CrClass instproc unknown { obj args } {
my log "unknown called with $obj $args" # When this happens, this is most likely an error. Ease debugging
# by writing the call stack to the error log.
::xo::show_stack
my log "::xo::db::CrClass: unknown called with $obj $args"
} }
# #
...@@ -1555,7 +1558,23 @@ namespace eval ::xo::db { ...@@ -1555,7 +1558,23 @@ namespace eval ::xo::db {
# remove as well vars and array starting with "__", assuming these # remove as well vars and array starting with "__", assuming these
# are volatile variables created by initialize_loaded_object or # are volatile variables created by initialize_loaded_object or
# similar mechanisms # similar mechanisms
foreach x [my info vars __*] {if {[my array exists $x]} {my array unset $x} {my unset $x}} set arrays {}
set scalars {}
foreach x [my info vars __*] {
if {[my array exists $x]} {
lappend arrays $x [my array get $x]
my array unset $x
} {
lappend scalars $x [my set $x]
my unset $x
}
}
return [list $arrays $scalars]
}
CrCache::Item instproc set_non_persistent_vars {vars} {
foreach {arrays scalars} $vars break
foreach {var value} $arrays {my array set $var $value}
foreach {var value} $scalars {my set $var $value}
} }
CrCache::Item instproc flush_from_cache_and_refresh {} { CrCache::Item instproc flush_from_cache_and_refresh {} {
# cache only names with IDs # cache only names with IDs
...@@ -1575,8 +1594,9 @@ namespace eval ::xo::db { ...@@ -1575,8 +1594,9 @@ namespace eval ::xo::db {
# session. # session.
set mixins [$obj info mixin] set mixins [$obj info mixin]
$obj mixin [list] $obj mixin [list]
$obj remove_non_persistent_vars set npv [$obj remove_non_persistent_vars]
ns_cache set xotcl_object_cache $obj [$obj serialize] ns_cache set xotcl_object_cache $obj [$obj serialize]
$obj set_non_persistent_vars $npv
$obj mixin $mixins $obj mixin $mixins
} else { } else {
# in any case, flush the canonical name # in any case, flush the canonical name
......
...@@ -703,7 +703,12 @@ namespace eval ::xo { ...@@ -703,7 +703,12 @@ namespace eval ::xo {
# this case, we do not have to perform the cond-notify. # this case, we do not have to perform the cond-notify.
if {[my exists_status $condition] && if {[my exists_status $condition] &&
[my get_status $condition] eq "COND_WAIT_REFRESH"} { [my get_status $condition] eq "COND_WAIT_REFRESH"} {
# Before, we had here COND_WAIT_TIMEOUT instead of }
if {[my exists_status $condition] &&
( [my get_status $condition] eq "COND_WAIT_REFRESH"
|| [my get_status $condition] eq "COND_WAIT_TIMEOUT")
} {
# Before, we had here one COND_WAIT_TIMEOUT, and once
# COND_WAIT_REFRESH # COND_WAIT_REFRESH
my set_status $condition $status $value my set_status $condition $status $value
catch {thread::cond notify $condition} catch {thread::cond notify $condition}
......
...@@ -35,6 +35,12 @@ namespace eval ::xo { ...@@ -35,6 +35,12 @@ namespace eval ::xo {
ical proc tcl_time_to_utc {time} { ical proc tcl_time_to_utc {time} {
clock format [clock scan $time] -format "%Y%m%dT%H%M%SZ" -gmt 1 clock format [clock scan $time] -format "%Y%m%dT%H%M%SZ" -gmt 1
} }
ical proc tcl_time_to_local_day {time} {
VALUE=DATE:[my clock_to_local_day [clock scan $time]]
}
ical proc utc_to_clock {utc_time} {
clock scan $utc_time -format "%Y%m%dT%H%M%SZ" -gmt 1
}
ical proc clock_to_utc {seconds} { ical proc clock_to_utc {seconds} {
clock format $seconds -format "%Y%m%dT%H%M%SZ" -gmt 1 clock format $seconds -format "%Y%m%dT%H%M%SZ" -gmt 1
} }
...@@ -80,6 +86,10 @@ namespace eval ::xo { ...@@ -80,6 +86,10 @@ namespace eval ::xo {
} }
namespace eval ::xo { namespace eval ::xo {
#
# VCALITEM is the superclass of a VTODO and a VEVENT, intended as an
# abstract class
#
Class create ::xo::ical::VCALITEM -parameter { Class create ::xo::ical::VCALITEM -parameter {
creation_date creation_date
last_modified last_modified
...@@ -93,6 +103,7 @@ namespace eval ::xo { ...@@ -93,6 +103,7 @@ namespace eval ::xo {
location location
geo geo
status status
{is_day_item false}
} }
::xo::ical::VCALITEM instproc tag {-tag -conv -value slot} { ::xo::ical::VCALITEM instproc tag {-tag -conv -value slot} {
...@@ -107,14 +118,43 @@ namespace eval ::xo { ...@@ -107,14 +118,43 @@ namespace eval ::xo {
} }
} }
if {[info exists conv]} { if {[info exists conv]} {
return "$tag:[::xo::ical $conv $value]\n" return "$tag:[::xo::ical $conv $value]\r\n"
} else { } else {
return "$tag:$value\n" return "$tag:$value\r\n"
} }
return "" return ""
} }
::xo::ical::VCALITEM instproc start_end {} {
if {[my is_day_item]} {
append result \
[my tag -conv tcl_time_to_local_day dtstart] \
[my tag -conv tcl_time_to_local_day dtend]
} else {
append result \
[my tag -conv tcl_time_to_utc dtstart] \
[my tag -conv tcl_time_to_utc dtend]
}
}
::xo::ical::VCALITEM instproc as_ical {} { ::xo::ical::VCALITEM instproc as_ical {} {
set item_type [namespace tail [my info class]]
append t "BEGIN:$item_type\r\n" \
[my ical_body] \
"END:$item_type\r\n"
return $t
}
::xo::ical::VCALITEM instproc ical_body {} {
#
# The method ical_body returns the ical-formatted content of the
# variables. All variables of VEVENTs and VTODOs are listed below,
# since the names are distinct, and no methods are used.
#
# So far there is no handling for the repetition fields (which
# might occur more than once). An option would be to handle these
# as lists.
#
my instvar creation_date last_modified dtstamp my instvar creation_date last_modified dtstamp
# #
# All date/time stamps are provided either by # All date/time stamps are provided either by
...@@ -133,8 +173,7 @@ namespace eval ::xo { ...@@ -133,8 +173,7 @@ namespace eval ::xo {
# VTODO: NEEDS-ACTION, COMPLETED, IN-PROCESS, CANCELLED # VTODO: NEEDS-ACTION, COMPLETED, IN-PROCESS, CANCELLED
# VJOURNAL: DRAFT, FINAL, CANCELLED # VJOURNAL: DRAFT, FINAL, CANCELLED
set item_type [namespace tail [my info class]] append t \
append t "BEGIN:$item_type\n" \
[my tag -conv tcl_time_to_utc -value $tcl_creation_date created] \ [my tag -conv tcl_time_to_utc -value $tcl_creation_date created] \
[my tag -conv tcl_time_to_utc -value $tcl_last_modified last-modified] \ [my tag -conv tcl_time_to_utc -value $tcl_last_modified last-modified] \
[my tag -conv tcl_time_to_utc -value $tcl_stamp dtstamp] \ [my tag -conv tcl_time_to_utc -value $tcl_stamp dtstamp] \
...@@ -142,16 +181,22 @@ namespace eval ::xo { ...@@ -142,16 +181,22 @@ namespace eval ::xo {
[my tag -conv tcl_time_to_utc dtend] \ [my tag -conv tcl_time_to_utc dtend] \
[my tag -conv tcl_time_to_utc completed] \ [my tag -conv tcl_time_to_utc completed] \
[my tag -conv tcl_time_to_utc percent-complete] \ [my tag -conv tcl_time_to_utc percent-complete] \
[my tag transp] \
[my tag uid] \ [my tag uid] \
[my tag url] \ [my tag url] \
[my tag geo] \ [my tag geo] \
[my tag priority] \ [my tag priority] \
[my tag sequence] \
[my tag CLASS] \
[my tag location] \ [my tag location] \
[my tag status] \ [my tag status] \
[my tag -conv text_to_ical description] \ [my tag -conv text_to_ical description] \
[my tag -conv text_to_ical summary] \ [my tag -conv text_to_ical summary] \
[my tag -conv tcl_time_to_utc due] \ [my tag -conv tcl_time_to_utc due]
"END:$item_type\n"
if {[my exists formatted_recurrences]} {
append t [my set formatted_recurrences]
}
return $t return $t
} }
# #
...@@ -200,7 +245,10 @@ namespace eval ::xo { ...@@ -200,7 +245,10 @@ namespace eval ::xo {
# just a stub for now # just a stub for now
Class create ::xo::ical::VEVENT -superclass ::xo::ical::VCALITEM -parameter { Class create ::xo::ical::VEVENT -superclass ::xo::ical::VCALITEM -parameter {
dtend dtend
} sequence
transp
formatted_recurrences
}
# #
# This class is designed to be a mixin for an ordered composite # This class is designed to be a mixin for an ordered composite
......
...@@ -87,7 +87,9 @@ ad_page_contract { ...@@ -87,7 +87,9 @@ ad_page_contract {
lappend reduced_sc $sc lappend reduced_sc $sc
} }
if {$reduced_sc eq {}} continue if {$reduced_sc eq {}} continue
append superclasses "[my dotquote $e]->[my dotquotel $reduced_sc];\n" foreach sc $reduced_sc {
append superclasses "[my dotquote $e]->[my dotquotel $sc];\n"
}
} }
set children "" set children ""
set mixins "" set mixins ""
...@@ -133,12 +135,12 @@ ad_page_contract { ...@@ -133,12 +135,12 @@ ad_page_contract {
dpi = $dpi; dpi = $dpi;
rankdir = BT; rankdir = BT;
node \[$font shape=record\]; $tclasses node \[$font shape=record\]; $tclasses
edge \[arrawohead=empty\]; $superclasses edge \[arrowhead=empty\]; $superclasses
node \[color=Green,shape=ellipse,fontcolor=Blue, style=filled, fillcolor=darkseagreen1\]; $objects node \[color=Green,shape=ellipse,fontcolor=Blue, style=filled, fillcolor=darkseagreen1\]; $objects
edge \[color=Blue,style=dotted\]; $instances edge \[color=Blue,style=dotted,arrowhead=normal,label=\"instance of\",fontsize=10\]; $instances
edge \[color=pink,arrowhead=diamond, style=dotted\]; $children edge \[color=pink,arrowhead=diamond, style=dotted\]; $children
edge \[label=instmixin,fontsize=10,color=$imcolor,fontcolor=$imcolor,arrowhead=none,arrowtail=vee, style=dashed,dir=back, constraint=0\]; $instmixins edge \[label=instmixin,fontsize=10,color=$imcolor,fontcolor=$imcolor,arrowhead=none,arrowtail=vee,style=dashed,dir=back,constraint=0\]; $instmixins
edge \[label=mixin,fontsize=10,color=$imcolor,fontcolor=$imcolor,arrowhead=none,arrowtail=vee, style=dashed,dir=back, constraint=0\]; $mixins edge \[label=mixin,fontsize=10,color=$imcolor,fontcolor=$imcolor,arrowhead=none,arrowtail=vee,style=dashed,dir=back,constraint=0\]; $mixins
}" }"
} }
...@@ -152,9 +154,11 @@ catch {set dot [::util::which dot]} ...@@ -152,9 +154,11 @@ catch {set dot [::util::which dot]}
if {$dot eq "" && [file executable /usr/bin/dot]} {set dot /usr/bin/dot} if {$dot eq "" && [file executable /usr/bin/dot]} {set dot /usr/bin/dot}
if {$dot eq ""} {ns_return 404 plain/text "do dot found"; ad_script_abort} if {$dot eq ""} {ns_return 404 plain/text "do dot found"; ad_script_abort}
set tmpfile [ns_tmpnam].png set tmpnam [ns_tmpnam]
set f [open "|$dot -Tpng -o $tmpfile" w] set tmpfile $tmpnam.png
puts $f $dot_code set f [open "|$dot -Tpng -o $tmpfile" w]; puts $f $dot_code; close $f
close $f
ns_returnfile 200 [ns_guesstype $tmpfile] $tmpfile ns_returnfile 200 [ns_guesstype $tmpfile] $tmpfile
file delete $tmpfile file delete $tmpfile
\ No newline at end of file
#set f [open $tmpnam.dot w]; puts $f $dot_code; close $f
#file delete $tmpnam.dot
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<inherit-templates-p>t</inherit-templates-p> <inherit-templates-p>t</inherit-templates-p>
<auto-mount>xotcl</auto-mount> <auto-mount>xotcl</auto-mount>
<version name="0.118" url="http://media.wu-wien.ac.at/download/xotcl-core-0.118.apm"> <version name="0.124" url="http://media.wu-wien.ac.at/download/xotcl-core-0.124.apm">
<owner url="mailto:neumann@wu-wien.ac.at">Gustaf Neumann</owner> <owner url="mailto:neumann@wu-wien.ac.at">Gustaf Neumann</owner>
<summary>XOTcl library functionality (e.g. thread handling, online documentation, Generic Form and List Classes)</summary> <summary>XOTcl library functionality (e.g. thread handling, online documentation, Generic Form and List Classes)</summary>
<release-date>2011-01-14</release-date> <release-date>2011-01-14</release-date>
...@@ -43,7 +43,8 @@ when components are reloaded. ...@@ -43,7 +43,8 @@ when components are reloaded.
<license>BSD-Style</license> <license>BSD-Style</license>
<maturity>0</maturity> <maturity>0</maturity>
<provides url="xotcl-core" version="0.118"/> <provides url="xotcl-core" version="0.124"/>
<requires url="acs-kernel" version="5.2.0"/>
<callbacks> <callbacks>
<callback type="before-install" proc="::xotcl-core::before_install_callback"/> <callback type="before-install" proc="::xotcl-core::before_install_callback"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment