Index: openacs-4/packages/proctoring-support/proctoring-support.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/proctoring-support.info,v diff -u -r1.1.2.25 -r1.1.2.26 --- openacs-4/packages/proctoring-support/proctoring-support.info 10 Feb 2022 09:32:58 -0000 1.1.2.25 +++ openacs-4/packages/proctoring-support/proctoring-support.info 10 Feb 2022 13:16:36 -0000 1.1.2.26 @@ -10,7 +10,7 @@ f proctoring - + Antonio Pisano Set of tools to implement proctoring of user interaction Wirtschaftsuniversität Wien @@ -21,7 +21,7 @@ No real UI is provided by the package itself. Other packages must integrate the provided includes. 0 - + Index: openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl,v diff -u -r1.1.2.13 -r1.1.2.14 --- openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl 10 Feb 2022 09:32:58 -0000 1.1.2.13 +++ openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl 10 Feb 2022 13:16:36 -0000 1.1.2.14 @@ -75,21 +75,15 @@ } set timestamp [clock seconds] - set file_path $proctoring_dir/${name}-${type}-$timestamp.$extension - file mkdir -- $proctoring_dir - file rename -force -- ${file.tmpfile} $file_path + set artifact [::proctoring::artifact::store \ + -object_id $object_id \ + -user_id $user_id \ + -timestamp $timestamp \ + -name $name \ + -type $type \ + -file ${file.tmpfile}] - # Create an entry in the database for the file we have just - # collected, so that we can further enrich it with metadata in - # later postprocessing phases. - ::xo::dc dml -prepare {integer integer integer text text text} init_artifact { - insert into proctoring_object_artifacts - (object_id, user_id, timestamp, name, type, file) - values - (:object_id, :user_id, to_timestamp(:timestamp), :name, :type, :file_path) - } - # Notify a websocket about the upload so that e.g. a UI can be updated # in real time. if {$notify_p} { @@ -102,7 +96,7 @@ "name": "$name", "type": "$type", "timestamp": "$timestamp", - "file": "$file_path" + "file": "[dict get $artifact file]" } }] Index: openacs-4/packages/proctoring-support/tcl/proctoring-callback-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/tcl/proctoring-callback-procs.tcl,v diff -u -r1.1.2.3 -r1.1.2.4 --- openacs-4/packages/proctoring-support/tcl/proctoring-callback-procs.tcl 9 Feb 2022 14:41:36 -0000 1.1.2.3 +++ openacs-4/packages/proctoring-support/tcl/proctoring-callback-procs.tcl 10 Feb 2022 13:16:36 -0000 1.1.2.4 @@ -67,3 +67,20 @@ @return dict with fields 'object_id' and 'object_url' } - + +namespace eval ::proctoring::callback {} +namespace eval ::proctoring::callback::artifact {} + +ad_proc -public -callback ::proctoring::callback::artifact::postprocess { + -artifact_id:required +} { + Implementations of this hook can apply custom postprocessing to a + proctoring artifact. + + Be aware that this callback is invoked as soon as the artifact is + created, for instance, at upload. Every callback implementation + should defer to background processing every operation that would + block a connection thread for a long time. + + @param artifact_id id of the artifact +} - Index: openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl,v diff -u -r1.1.2.14 -r1.1.2.15 --- openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl 9 Feb 2022 14:50:42 -0000 1.1.2.14 +++ openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl 10 Feb 2022 13:16:36 -0000 1.1.2.15 @@ -256,3 +256,84 @@ return $already_received_p } +namespace eval ::proctoring {} +namespace eval ::proctoring::artifact {} + +ad_proc ::proctoring::artifact::store { + -object_id:required + -user_id:required + -timestamp + -name:required + -type:required + -file:required +} { + Stores a file as a new artifacts and invoke the postprocessing + callbacks. + + @param object_id id of the proctored object + @param user_id id of the user this artifact was created for + @param timestamp epoch in seconds. Defaults to clock seconds when + not specified + @param name name of the source for this artifact (e.g. 'camera' or 'desktop') + @param type type of artifact (e.g. 'image' or 'audio') + @param file absolute path to the artifact file. The file will be + moved inside of the proctoring folde r, so this can be + a tempfile from a request. + + @return dict of fields 'artifact_id' and 'file'. File is the final + path of the file in the proctoring folder. +} { + if {![file exists $file]} { + error "File does not exist" + } + + if {![info exists timestamp]} { + set timestamp [clock seconds] + } + + set proctoring_dir [::proctoring::folder \ + -object_id $object_id \ + -user_id $user_id] + + set file_path $proctoring_dir/${name}-${type}-${timestamp}[file extension $file] + file rename -force -- $file $file_path + + # Create an entry in the database for the file we have just + # collected, so that we can further enrich it with metadata in + # later postprocessing phases. + set artifact_id [::xo::dc get_value -prepare { + integer integer integer text text text + } store { + with insert as ( + insert into proctoring_object_artifacts + ( + artifact_id, + object_id, + user_id, + timestamp, + name, + type, + file + ) + values + ( + default, + :object_id, + :user_id, + to_timestamp(:timestamp), + :name, + :type, + :file_path + ) + returning artifact_id + ) + select artifact_id from insert + }] + + callback ::proctoring::callback::artifact::postprocess \ + -artifact_id $artifact_id + + return [list \ + artifact_id $artifact_id \ + file $file_path] +} Index: openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl,v diff -u -r1.1.2.12 -r1.1.2.13 --- openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl 16 Jun 2021 11:26:55 -0000 1.1.2.12 +++ openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl 10 Feb 2022 13:16:36 -0000 1.1.2.13 @@ -264,6 +264,130 @@ } } +aa_register_case \ + -cats {api smoke} \ + -procs { + ::proctoring::artifact::store + } \ + proctoring_artifact_store { + Test ::proctoring::artifact::store + } { + set file [ad_tmpnam].test + set wfd [open $file w] + puts $wfd 1234 + close $wfd + + set object_id [::xo::dc get_value get_object_id { + select max(object_id) from acs_objects + }] + set user_id [::xo::dc get_value get_user_id { + select max(user_id) from users + }] + set name test + set type code + set timestamp [clock scan "2016-09-07" -format %Y-%m-%d] + + aa_section "Initial cleanup" + ::xo::dc dml cleanup { + delete from proctoring_object_artifacts + where name = :name + and type = :type + and timestamp = to_timestamp(:timestamp) + and user_id = :user_id + and object_id = :object_id + } + + aa_section "Storing an artifact correctly" + set artifact [::proctoring::artifact::store \ + -object_id $object_id \ + -user_id $user_id \ + -timestamp $timestamp \ + -name $name \ + -type $type \ + -file $file] + + set artifact_id [dict get $artifact artifact_id] + set file [dict get $artifact file] + aa_true "Artifact '$artifact_id' on file '$file' was created" [::xo::dc 0or1row check { + select 1 from proctoring_object_artifacts + where artifact_id = :artifact_id + and file = :file + and name = :name + and type = :type + and timestamp = to_timestamp(:timestamp) + and user_id = :user_id + and object_id = :object_id + }] + aa_true "File exists" [file exists $file] + aa_equals "File has the original extension" .test [file extension $file] + + aa_section "Cleanup" + ::xo::dc dml cleanup { + delete from proctoring_object_artifacts + where artifact_id = :artifact_id + } + aa_equals "Row was deleted" [db_resultrows] 1 + file delete -- $file + aa_false "File was removed" [file exists $file] + + aa_section "Try to store for an invalid object" + set broken_object_id [::xo::dc get_value get_broken_object_id { + select min(object_id) - 1 from acs_objects + }] + aa_true "Saving for an invalid object fails" [catch { + ::proctoring::artifact::store \ + -object_id $broken_object_id \ + -user_id $user_id \ + -timestamp $timestamp \ + -name $name \ + -type $type \ + -file $file + }] + aa_false "No row can be found for broken object '$broken_object_id'" [::xo::dc 0or1row check { + select 1 from proctoring_object_artifacts + where object_id = :broken_object_id + fetch first 1 rows only + }] + + aa_section "Try to store for an invalid user" + set broken_user_id [::xo::dc get_value get_broken_user_id { + select min(user_id) - 1 from users + }] + aa_true "Saving for an invalid user fails" [catch { + ::proctoring::artifact::store \ + -object_id $object_id \ + -user_id $broken_user_id \ + -timestamp $timestamp \ + -name $name \ + -type $type \ + -file $file + }] + aa_false "No row can be found for broken user '$broken_user_id'" [::xo::dc 0or1row check { + select 1 from proctoring_object_artifacts + where user_id = :broken_user_id + fetch first 1 rows only + }] + + aa_section "Try to store a non-existing file" + set broken_file [ad_tmpnam] + aa_true "Saving a non-existing file fails" [catch { + ::proctoring::artifact::store \ + -object_id $object_id \ + -user_id $user_id \ + -timestamp $timestamp \ + -name $name \ + -type $type \ + -file $broken_file + }] + aa_false "No row can be found for non-existing file '$broken_file'" [::xo::dc 0or1row check { + select 1 from proctoring_object_artifacts + where file = :broken_file + and object_id = :object_id + and user_id = :user_id + fetch first 1 rows only + }] + } + # Local variables: # mode: tcl # tcl-indent-level: 4