my$pg= Mojo::Pg->new->dsn($config->get('dbistring')) or die"Cannot connect to database!";
=head1 NAME
SReview::Talk - Database abstraction for talks in the SReview database
=head1 SYNOPSIS
use SReview::Talk;
my $talk = SReview::Talk->new(talkid => 1);
print $talk->nonce;
my $nonce = $talk->nonce;
my $talk_alt = SReview::Talk->by_nonce($nonce);
print $talk_alt->talkid; # 1
$talk->add_correction(length_adj => 1);
$talk->done_correcting;
=head1 DESCRIPTION
SReview::Talk provides a (Moose-based) object-oriented interface to the
data related to a talk that is stored in the SReview database. Although
it is not yet used everywhere, the intention is for it to eventually
replace all the direct PostgreSQL calls.
=head1 PROPERTIES
=head2 talkid
The unique ID of the talk. Required attribute at construction time (but
see the C<by_nonce> method, below). Is used to look up the relevant data
in the database.
=cut
has'talkid'=> (
required=> 1,
is=> 'ro',
trigger=> sub{
my$self= shift;
my$val= shift;
my$st= $pg->db->dbh->prepare("SELECT count(*) FROM talks WHERE id = ?");
$st->execute($val);
die"Talk does not exist.\n"unless$st->rows == 1;
},
);
has'upstreamid'=> (
is=> 'ro',
isa=> 'Str',
builder=> '_probe_upstreamid',
lazy=> 1,
);
sub_probe_upstreamid {
returnshift->_get_pathinfo->{raw}{upstreamid}
}
=head2 pathinfo
Helper property to look up information from the database. Should not be
used directly.
=cut
has'pathinfo'=> (
lazy=> 1,
is=> 'bare',
builder=> '_load_pathinfo',
reader=> '_get_pathinfo',
);
sub_load_pathinfo {
my$self= shift;
my$pathinfo= {};
my$eventdata= $pg->db->dbh->prepare("SELECT events.id AS eventid, events.name AS event, events.outputdir AS event_output, rooms.name AS room, rooms.outputname AS room_output, rooms.id AS room_id, talks.starttime, talks.starttime::date AS date, to_char(starttime, 'DD Month yyyy at HH:MI') AS readable_date, to_char(talks.starttime, 'yyyy') AS year, talks.endtime, talks.slug, talks.title, talks.subtitle, talks.state, talks.progress, talks.nonce, talks.apologynote, talks.upstreamid FROM talks JOIN events ON talks.event = events.id JOIN rooms ON rooms.id = talks.room WHERE talks.id = ?");
my$st= $pg->db->dbh->prepare("WITH orderedlog(talk, comment, logdate) AS (SELECT talk, comment, logdate FROM commentlog ORDER BY logdate DESC) SELECT talk, string_agg(logdate || E'\n' || comment, E'\n\n') AS comments FROM orderedlog WHERE talk = ? GROUP BY talk");
$st->execute($self->talkid);
my$row= $st->fetchrow_hashref;
return$row->{comments};
}
=head2 first_comment
The most recent comment entered in the "other brokenness" field.
=cut
has'first_comment'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_first_comment',
clearer=> 'clear_first_comment',
predicate=> 'has_first_comment',
);
sub_load_first_comment {
my$self= shift;
my$st= $pg->db->dbh->prepare("WITH orderedlog(talk, comment) AS (SELECT talk, comment FROM commentlog ORDER BY logdate DESC) SELECT talk, logdate FROM orderedlog WHERE talk = ? LIMIT 1");
$st->execute($self->talkid);
my$row= $st->fetchrow_hashref;
return$row->{comment};
}
=head2 corrected_times
The start- and endtime of the talk, with corrections (if any) applied.
=cut
has'corrected_times'=> (
lazy=> 1,
is=> 'ro',
builder=> '_load_corrected_times',
);
sub_load_corrected_times {
my$self= shift;
my$times= {};
my$st= $pg->db->dbh->prepare("SELECT starttime, to_char(starttime, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isostart, endtime, to_char(endtime, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isoend from talks WHERE id = ?");
$st->execute($self->talkid);
die"talk lost"unless$st->rows > 0;
my$row= $st->fetchrow_hashref();
$times->{start} = $row->{starttime};
$times->{end} = $row->{endtime};
$times->{start_iso} = $row->{isostart};
$times->{end_iso} = $row->{isoend};
$st= $pg->db->dbh->prepare("SELECT coalesce(talks.starttime + (corrections.property_value || ' seconds')::interval, talks.starttime) AS corrected_time, to_char(coalesce(talks.starttime + (corrections.property_value || ' seconds')::interval, talks.starttime), 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isotime FROM talks LEFT JOIN corrections ON talks.id = corrections.talk LEFT JOIN properties ON properties.id = corrections.property WHERE talks.id = ? AND properties.name = 'offset_start'");
$st->execute($self->talkid);
if($st->rows > 0) {
$row= $st->fetchrow_hashref();
$times->{start} = $row->{corrected_time};
$times->{start_iso} = $row->{isotime};
}
$st= $pg->db->dbh->prepare("SELECT corrected_time, to_char(corrected_time, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isotime FROM (select ?::timestamptz + (talks.endtime - talks.starttime) + (coalesce(corrections.property_value, '0') || ' seconds')::interval AS corrected_time FROM talks LEFT JOIN corrections ON talks.id = corrections.talk LEFT JOIN properties ON properties.id = corrections.property WHERE talks.id = ? AND properties.name = 'length_adj') AS sq");
$st->execute($times->{start}, $self->talkid);
if($st->rows > 0) {
$row= $st->fetchrow_hashref();
$times->{end} = $row->{corrected_time};
$times->{end_iso} = $row->{isotime};
}
return$times;
}
=head2 nonce
The talk's unique hex string, used to look it up for review.
=cut
has'nonce'=> (
is=> 'rw',
builder=> '_load_nonce',
lazy=> 1,
);
sub_load_nonce {
returnshift->_get_pathinfo->{raw}{nonce};
}
=head2 date
The date on which the talk happened
=cut
has'date'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_date',
);
sub_load_date {
returnshift->_get_pathinfo->{raw}{date};
}
=head2 readable_date
The date on which the talk happened, in a (somewhat) more human-readable
format than the C<date> property.
=cut
has'readable_date'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_readable_date',
);
sub_load_readable_date {
returnshift->_get_pathinfo->{raw}{readable_date};
}
=head2 eventname
The name of the event of which this talk is part
=cut
has'eventname'=> (
lazy=> 1,
is=> 'ro',
builder=> '_load_eventname',
);
sub_load_eventname {
my$self= shift;
return$self->_get_pathinfo->{raw}{event};
}
=head2 event_output
The name of the event as used in output directories, if any.
=cut
has'event_output'=> (
lazy=> 1,
is=> 'ro',
builder=> '_load_event_output',
);
sub_load_event_output {
my$self= shift;
my$rv= $self->_get_pathinfo->{raw}{event_output};
if(!defined($rv) || length($rv) == 0) {
$rv= $self->_get_pathinfo->{raw}{event};
$rv=~ s/[^a-zA-Z0-9]/-/g;
}
return$rv;
}
=head2 progress
The current progress value of the talk, as an L<SReview::Talk::Progress>
A short, safe representation of the talk; used for filenames.
=cut
has'slug'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_slug',
);
sub_load_slug {
my$self= shift;
return$self->_get_pathinfo->{"slug"};
}
=head2 corrections
The corrections that are set on this talk.
Supports:
=over
=item has_correction
check whether a correction exists (by name)
=item set_correction
Overwrite a correction with a new value
=item clear_correction
Remove a correction from the set of corrections
=item correction_pairs
Get a key/value list of corrections
=back
=cut
has'corrections'=> (
traits=> ['Hash'],
isa=> 'HashRef[Str]',
lazy=> 1,
is=> 'rw',
builder=> '_load_corrections',
clearer=> '_clear_corrections',
handles=> {
has_correction=> 'exists',
set_correction=> 'set',
clear_correction=> 'delete',
correction_pairs=> 'kv',
},
);
sub_load_corrections {
my$self= shift;
my$corrections_data= $pg->db->dbh->prepare("SELECT corrections.talk, properties.name AS property, corrections.property_value FROM corrections LEFT JOIN properties ON corrections.property = properties.id WHERE talk = ?");
Gets a list of hashes with data on the fragments of video files that are
necessary to build the talk, given the schedule and the current
corrections.
Each hash contains:
=over
=item talkid
The talk ID for fragments that are part of the main video; -1 for
fragments that are part of the pre video; and -2 for fragments that are
part of the post video.
=item rawid
The unique ID of the raw file
=item raw_filename
The filename of the raw file
=item fragment_start
The offset into the raw file where the interesting content begins.
=item raw_length
The length of the entire video (should be the same for each fragment)
=item raw_length_corrected
The length of the interesting content in I<this> raw file
=back
=cut
has'video_fragments'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_video_fragments',
);
sub_load_video_fragments {
my$self= shift;
my$corrections= $self->corrections;
my$talk_data= $pg->db->dbh->prepare("SELECT talkid, rawid, raw_filename, extract(epoch from fragment_start) AS fragment_start, extract(epoch from raw_length) as raw_length, extract(epoch from raw_length_corrected) as raw_length_corrected FROM adjusted_raw_talks(?, make_interval(secs :=?::numeric), make_interval(secs := ?::numeric)) ORDER BY talk_start, raw_start");
The same values as the video_fragments attribute, but with every length
extended as needed for A/V sync operations.
=cut
has'avs_video_fragments'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_avs_video_fragments',
);
sub_load_avs_video_fragments {
my$self= shift;
my$corrections= $self->corrections;
if($corrections->{offset_audio} == 0) {
return$self->video_fragments;
}
my$talk_data= $pg->db->dbh->prepare("SELECT talkid, rawid, raw_filename, extract(epoch from fragment_start) as fragment_start, extract(epoch from raw_length) as raw_length, extract(epoch from raw_length_corrected) as raw_length_corrected from adjusted_raw_talks(?, make_interval(secs :=?::numeric), make_interval(secs := ?::numeric), make_interval(secs :=abs(?::numeric))) order by talk_start, raw_start");
my$query= $pg->db->dbh->prepare("SELECT speakers.name FROM speakers JOIN speakers_talks ON speakers.id = speakers_talks.speaker WHERE speakers_talks.talk = ? ORDER BY speakers.name");
$query->execute($self->talkid);
my$rv= [];
while(my$talk= $query->fetchrow_arrayref) {
push@$rv, $talk->[0];
}
return$rv;
}
=head2 room
The room in which the talk happened/will happen
=cut
has'room'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_room',
);
sub_load_room {
returnshift->_get_pathinfo->{raw}{room};
}
=head2 roomid
The unique ID of the room
=cut
has'roomid'=> (
lazy=> 1,
is=> 'rw',
builder=> '_load_roomid',
);
sub_load_roomid {
returnshift->_get_pathinfo->{raw}{room_id}
}
=head2 eventurl
The URL for the talk on the event's website. Only contains data if
C<$eventurl_format> is set in the config file; if it doesn't, returns
$db->prepare("UPDATE talks SET apologynote=? WHERE id = ?")->execute($self->apology, $self->talkid);
}
if($self->_has_flags) {
$db->prepare("UPDATE talks SET flags=? WHERE id = ?")->execute(encode_json($self->flags), $self->talkid);
}
if($self->has_stream) {
$db->prepare("UPDATE talks SET active_stream=? WHERE id = ?")->execute($self->active_stream, $self->talkid);
}
}
=head2 set_state
Override the state of the talk to a new state, ignoring the state
transitions. Note, does not update the object, so this should be done
just before destroying it.
=cut
subset_state {
my$self= shift;
my$newstate= shift;
my$progress= shift;
$progress= 'waiting'unlessdefined($progress);
my$dbh= $pg->db->dbh;
my$st= $dbh->prepare("UPDATE talks SET state=?, progress=? WHERE id=?") or die$dbh->errstr;
$st->execute($newstate, $progress, $self->talkid) or die$dbh->errstr;
}
=head2 state_done
Set the progress to "done" in the given state. Does nothing if the talk
has since moved to another state.
=cut
substate_done {
my$self= shift;
my$state= shift;
my$st= $pg->db->dbh->prepare("UPDATE talks SET progress='done' WHERE state = ? AND id = ?");
$st->execute($state, $self->talkid);
}
=head2 reset_corrections
Clear all corrections, except the serial one. Used when a user requests
that the talk be reset to default.
=cut
subreset_corrections {
my$self= shift;
$self->add_correction(serial=> 1);
$pg->db->dbh->prepare("DELETE FROM corrections WHERE talk = ? AND property NOT IN (SELECT id FROM properties WHERE name = 'serial')")->execute($self->talkid) or die$!;
}
noMoose;
1;
Keyboard Shortcuts
Global
s
Focus search bar
?
Bring up this help dialog
GitHub
gp
Go to pull requests
gi
go to github issues (only if github is preferred repository)