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.24 -r1.1.2.25 --- openacs-4/packages/proctoring-support/proctoring-support.info 9 Feb 2022 14:10:55 -0000 1.1.2.24 +++ openacs-4/packages/proctoring-support/proctoring-support.info 10 Feb 2022 09:32:58 -0000 1.1.2.25 @@ -10,7 +10,7 @@ f proctoring - + Antonio Pisano Set of tools to implement proctoring of user interaction Wirtschaftsuniversität Wien @@ -21,11 +21,12 @@ 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-display.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/lib/proctoring-display.adp,v diff -u -r1.1.2.3 -r1.1.2.4 --- openacs-4/packages/proctoring-support/lib/proctoring-display.adp 31 Mar 2021 17:19:20 -0000 1.1.2.3 +++ openacs-4/packages/proctoring-support/lib/proctoring-display.adp 10 Feb 2022 09:32:58 -0000 1.1.2.4 @@ -127,7 +127,7 @@
  • -

    @events.timestamp_pretty@

    +

    @events.timestamp@

    @@ -165,7 +165,16 @@ function createEvent(e) { var event = template.cloneNode(true); var title = event.querySelector("[name=title]"); - var timestamp = new Date(e.timestamp * 1000).toISOString().substring(0, 19).replace("T", " "); + var timestamp = new Date(e.timestamp * 1000); + // Compute the local ISO date. toISOString would + // return the UTC time... + timestamp = + (timestamp.getFullYear() + '').padStart(4, '0') + '-' + + ((timestamp.getMonth() + 1) + '').padStart(2, '0') + '-' + + (timestamp.getDate() + '').padStart(2, '0') + ' ' + + (timestamp.getHours() + '').padStart(2, '0') + ':' + + (timestamp.getMinutes() + '').padStart(2, '0') + ':' + + (timestamp.getSeconds() + '').padStart(2, '0'); title.textContent = timestamp; eventList.appendChild(event); return event; Index: openacs-4/packages/proctoring-support/lib/proctoring-display.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/lib/proctoring-display.tcl,v diff -u -r1.1.2.5 -r1.1.2.6 --- openacs-4/packages/proctoring-support/lib/proctoring-display.tcl 11 Jun 2021 14:53:15 -0000 1.1.2.5 +++ openacs-4/packages/proctoring-support/lib/proctoring-display.tcl 10 Feb 2022 09:32:58 -0000 1.1.2.6 @@ -73,6 +73,11 @@ if {$delete_p && [llength $user_id] >= 1} { foreach u $user_id { + ::xo::dc dml -prepare {integer integer} delete_artifacts { + delete from proctoring_object_artifacts + where object_id = :object_id + and user_id = :u + } set folder [::proctoring::folder \ -object_id $object_id -user_id $u] file delete -force -- $folder @@ -106,58 +111,64 @@ set portrait_url [export_vars -base "/shared/portrait-bits.tcl" {user_id {size x200}}] set back_url $base_url - set camera_pics [lsort -increasing -dictionary \ - [glob -nocomplain -directory $folder camera-image-*.*]] - set desktop_pics [lsort -increasing -dictionary \ - [glob -nocomplain -directory $folder desktop-image-*.*]] - set rows [list] - foreach camera_pic $camera_pics desktop_pic $desktop_pics { - set row [dict create \ - audio_url ""] - if {$camera_pic ne ""} { - set camera_pic [file tail $camera_pic] - regexp {^camera-image-(\d+)\.\w+$} $camera_pic m camera_timestamp - dict set row camera_url [export_vars -base $user_url {{file $camera_pic}}] - } else { - dict set row camera_url "" - } + db_multirow events get_artifacts { + select camera.file as camera_url, + desktop.file as desktop_url, + coalesce(camera.timestamp, + desktop.timestamp) as timestamp, + null as audio_url + from (select timestamp, + file, + rank() over ( + partition by object_id, user_id + order by timestamp asc + ) as order + from proctoring_object_artifacts + where object_id = :object_id + and user_id = :user_id + and type = 'image' + and name = 'camera') camera + join + (select timestamp, + file, + rank() over ( + partition by object_id, user_id + order by timestamp asc + ) as order + from proctoring_object_artifacts + where object_id = :object_id + and user_id = :user_id + and type = 'image' + and name = 'desktop') desktop + on camera.order = desktop.order - if {$desktop_pic ne ""} { - set desktop_pic [file tail $desktop_pic] - regexp {^desktop-image-(\d+)\.\w+$} $desktop_pic m desktop_timestamp - dict set row desktop_url [export_vars -base $user_url {{file $desktop_pic}}] - } else { - dict set row desktop_url "" - } + union - if {[info exists camera_timestamp]} { - set timestamp $camera_timestamp - } else { - set timestamp $desktop_timestamp - } - dict set row timestamp $timestamp - dict set row timestamp_pretty [clock format $timestamp -format "%y-%m-%d %H:%M:%S"] + select null as camera_url, + null as desktop_url, + timestamp, + file as audio_url + from proctoring_object_artifacts + where object_id = :object_id + and user_id = :user_id + and type = 'audio' - lappend rows $row + order by timestamp asc + } { + if {$camera_url ne ""} { + set camera_url [file tail $camera_url] + set camera_url [export_vars -base $user_url {{file $camera_url}}] + } + if {$desktop_url ne ""} { + set desktop_url [file tail $desktop_url] + set desktop_url [export_vars -base $user_url {{file $desktop_url}}] + } + if {$audio_url ne ""} { + set audio_url [file tail $audio_url] + set audio_url [export_vars -base $user_url {{file $audio_url}}] + } } - - set audios [glob -nocomplain -directory $folder *-audio-*.*] - foreach audio $audios { - set row [dict create] - set audio [file tail $audio] - regexp {^\w+-audio-(\d+)\.\w+$} $audio m timestamp - dict set row audio_url [export_vars -base $user_url {{file $audio}}] - dict set row camera_url "" - dict set row desktop_url "" - dict set row timestamp $timestamp - dict set row timestamp_pretty [clock format $timestamp -format "%y-%m-%d %H:%M:%S"] - - lappend rows $row - } - - template::util::list_to_multirow events $rows - template::multirow sort events timestamp } } else { # List of proctored users @@ -169,32 +180,34 @@ set delete_confirm [_ xowiki.delete_all_confirm] if {$delete_p} { + ::xo::dc dml -prepare integer delete_artifacts { + delete from proctoring_object_artifacts + where object_id = :object_id + } file delete -force -- $folder ad_returnredirect $base_url ad_script_abort } - set rows [list] - foreach user_folder [glob -type d -nocomplain -directory $folder *] { - set row [dict create] - set proctored_user_id [file tail $user_folder] - dict set row user_id $proctored_user_id + db_multirow -extend { + student_id + proctoring_url + portrait_url + filter + } -unclobber users get_users { + select distinct a.user_id, + p.first_names, + p.last_name + from proctoring_object_artifacts a, + persons p + where object_id = :object_id + and a.user_id = p.person_id + order by last_name asc, first_names asc + } { + set student_id [::party::email -party_id $user_id] - set user [acs_user::get -user_id $proctored_user_id] - if {$user eq ""} {continue} - - set first_names [dict get $user first_names] - set last_name [dict get $user last_name] - - dict set row user_id $proctored_user_id - dict set row first_names $first_names - dict set row last_name $last_name - dict set row proctoring_url [export_vars -no_base_encode -base $base_url {{user_id $proctored_user_id} {object_id $object_id}}] - dict set row portrait_url /shared/portrait-bits.tcl?user_id=$proctored_user_id - dict set row filter [string tolower "$last_name $first_names"] - lappend rows $row + set proctoring_url [export_vars -no_base_encode -base $base_url { user_id object_id }] + set portrait_url /shared/portrait-bits.tcl?user_id=$user_id + set filter [string tolower "$last_name $first_names $student_id"] } - template::util::list_to_multirow users $rows - template::multirow sort users first_names - template::multirow sort users last_name } 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.12 -r1.1.2.13 --- openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl 9 Feb 2022 15:09:51 -0000 1.1.2.12 +++ openacs-4/packages/proctoring-support/lib/proctoring-upload.tcl 10 Feb 2022 09:32:58 -0000 1.1.2.13 @@ -80,6 +80,16 @@ file mkdir -- $proctoring_dir file rename -force -- ${file.tmpfile} $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. + ::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} { Index: openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql,v diff -u -r1.1.2.6 -r1.1.2.7 --- openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql 6 Aug 2021 11:31:12 -0000 1.1.2.6 +++ openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql 10 Feb 2022 09:32:58 -0000 1.1.2.7 @@ -66,4 +66,49 @@ -- access to this exam ); +-- +-- The proctoring_object_artifacts table is meant to store information +-- for each file collected during proctoring and provide a technical +-- space to store additional metadata coming from e.g. postprocessing +-- happening at a later phase. +-- The metadata column is a JSON so that the particular type of +-- information we store is flexible and can depend on the different +-- kind of file or even be extended by downstream integrations. +-- +create table proctoring_object_artifacts ( + artifact_id serial primary key, + -- we might have referenced the proctoring_objects table rather + -- than acs_objects, but this makes the data model more + -- flexible for those integrations that do not store the + -- proctoring configuration in proctoring_objects (e.g. xowf) + object_id integer references acs_objects(object_id) on delete cascade, + user_id integer references users(user_id) on delete cascade, + timestamp timestamp not null default current_timestamp, + name text not null, + type text not null, + file text not null, + metadata jsonb +); + +create index proctoring_object_artifacts_object_id_idx on + proctoring_object_artifacts(object_id); + +create index proctoring_object_artifacts_user_id_idx on + proctoring_object_artifacts(user_id); + +create index proctoring_object_artifacts_timestamp_idx on + proctoring_object_artifacts(timestamp); + +create index proctoring_object_artifacts_name_idx on + proctoring_object_artifacts(name); + +create index proctoring_object_artifacts_type_idx on + proctoring_object_artifacts(type); + +create index proctoring_object_artifacts_file_idx on + proctoring_object_artifacts(file); + +create unique index proctoring_object_artifacts_un_idx on + proctoring_object_artifacts(object_id, user_id, timestamp, name, type); + end; Index: openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql,v diff -u -r1.1.2.2 -r1.1.2.3 --- openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql 9 Jun 2021 15:55:54 -0000 1.1.2.2 +++ openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-drop.sql 10 Feb 2022 09:32:58 -0000 1.1.2.3 @@ -2,3 +2,4 @@ drop table proctoring_objects; drop table proctoring_examination_statement_acceptance; drop table proctoring_safe_exam_browser_conf; +drop table proctoring_object_artifacts; Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/proctoring-support/sql/postgresql/upgrade/upgrade-3.0.0-3.1.0.sql'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/proctoring-support/tcl/apm-callback-procs.tcl'. Fisheye: No comparison available. Pass `N' to diff? Index: openacs-4/packages/proctoring-support/www/upload.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/www/upload.tcl,v diff -u -r1.1.2.4 -r1.1.2.5 --- openacs-4/packages/proctoring-support/www/upload.tcl 21 Nov 2021 20:26:07 -0000 1.1.2.4 +++ openacs-4/packages/proctoring-support/www/upload.tcl 10 Feb 2022 09:32:59 -0000 1.1.2.5 @@ -7,7 +7,7 @@ file file.tmpfile {check_active_p:boolean true} - {notify_p:boolean false} + {notify_p:boolean true} {record_p:boolean true} } #ns_log notice "UPLOAD called with notify_p $notify_p record_p $record_p"