File Coverage

blib/lib/Elive/Entity/Session.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Elive::Entity::Session;
2 3     3   2506 use warnings; use strict;
  3     3   5  
  3         104  
  3         16  
  3         7  
  3         96  
3              
4 3     3   18 use Mouse;
  3         5  
  3         23  
5 3     3   1174 use Mouse::Util::TypeConstraints;
  3         7  
  3         24  
6              
7 3     3   258 use Carp;
  3         7  
  3         279  
8              
9             extends 'Elive::Entity';
10              
11 3     3   2047 use Elive::Entity::Meeting;
  0            
  0            
12             use Elive::Entity::MeetingParameters;
13             use Elive::Entity::ServerParameters;
14             use Elive::Entity::ParticipantList;
15             use Elive::Entity::Participants;
16             use Elive::DAO::Array;
17              
18             =head1 NAME
19              
20             Elive::Entity::Session - Session insert/update via ELM 3.x
21              
22             =head1 DESCRIPTION
23              
24             Elive::Entity::Session creates and modifies meetings via the C
25             and C commands, introduced with Elluminate Live! Manager 3.0.
26              
27             =cut
28              
29             __PACKAGE__->entity_name('Session');
30             __PACKAGE__->collection_name('Sessions');
31              
32             has 'id' => (is => 'rw', isa => 'Int', required => 1);
33             __PACKAGE__->primary_key('id');
34             __PACKAGE__->_alias(meetingId => 'id');
35             __PACKAGE__->_alias(sessionId => 'id');
36              
37             __PACKAGE__->params(
38              
39             preloadIds => 'Elive::Entity::Preloads',
40              
41             invitedParticipantsList => 'Elive::DAO::Array',
42             invitedModerators => 'Elive::DAO::Array',
43             invitedGuests => 'Elive::DAO::Array',
44              
45             timeZone => 'Str',
46             until => 'HiResDate',
47             repeatEvery => 'Int',
48             repeatSessionInterval => 'Int',
49             repeatSessionMonthlyInterval => 'Int',
50             repeatSessionMonthlyDay => 'Int',
51              
52             sundaySessionIndicator => 'Bool',
53             mondaySessionIndicator => 'Bool',
54             tuesdaySessionIndicator => 'Bool',
55             wednesdaySessionIndicator => 'Bool',
56             thursdaySessionIndicator => 'Bool',
57             fridaySessionIndicator => 'Bool',
58             saturdaySessionIndicator => 'Bool',
59              
60             );
61              
62             __PACKAGE__->mk_classdata(_delegates => {
63             meeting => 'Elive::Entity::Meeting',
64             meetingParameters => 'Elive::Entity::MeetingParameters',
65             serverParameters => 'Elive::Entity::ServerParameters',
66             participantList => 'Elive::Entity::ParticipantList',
67             });
68              
69             sub _delegate {
70             my $pkg = shift;
71              
72             our %handled = (meetingId => 1, url => 1);
73             my $delegates = $pkg->_delegates;
74              
75             foreach my $prop (sort keys %$delegates) {
76             my $class = $delegates->{$prop};
77             my $aliases = $class->_get_aliases;
78             my @delegates = grep {!$handled{$_}++} ($class->properties, $class->derivable, sort keys %$aliases);
79             push (@delegates, qw{buildJNLP check_preload add_preload remove_preload is_participant is_moderator list_preloads list_recordings})
80             if $prop eq 'meeting';
81             has $prop
82             => (is => 'rw', isa => $class, coerce => 1,
83             handles => \@delegates, lazy => 1,
84             default => sub {$class->retrieve($_[0]->id, reuse => 1, connection => $_[0]->connection)},
85             );
86             }
87             }
88              
89             __PACKAGE__->_delegate;
90              
91             ## ELM 3.x mappings follow
92              
93             __PACKAGE__->_alias(allPermissionsMeeting => 'fullPermissions', freeze => 1);
94              
95             __PACKAGE__->_alias(boundaryTime => 'boundaryMinutes', freeze => 1);
96              
97             __PACKAGE__->_alias(enableTeleconferencing => 'enableTelephony', freeze => 1);
98              
99             __PACKAGE__->_alias(facilitator => 'facilitatorId', freeze => 1);
100              
101             __PACKAGE__->_alias(MaxVideoWindows => 'VideoWindow', freeze => 1);
102              
103             __PACKAGE__->_alias(moderatorTeleconferenceAddress => 'moderatorTelephonyAddress', freeze => 1);
104              
105             __PACKAGE__->_alias(moderatorTeleconferencePIN => 'moderatorTelephonyPIN', freeze => 1);
106              
107             __PACKAGE__->_alias(participantTeleconferenceAddress => 'participantTelephonyAddress', freeze => 1);
108              
109             __PACKAGE__->_alias(participantTeleconferencePIN => 'participantTelephonyPIN', freeze => 1);
110              
111             __PACKAGE__->_alias(private => 'privateMeeting', freeze => 1);
112              
113             __PACKAGE__->_alias(reservedSeatCount => 'seats', freeze => 1);
114              
115             __PACKAGE__->_alias(restrictParticipants => 'restrictedMeeting', freeze => 1);
116              
117             __PACKAGE__->_alias(supervisedMeeting => 'supervised', freeze => 1);
118              
119             __PACKAGE__->_alias(sessionServerTeleconferenceType => 'telephonyType', freeze => 1);
120              
121             __PACKAGE__->_alias(serverTeleconferenceAddress => 'serverTelephonyAddress', freeze => 1);
122              
123             __PACKAGE__->_alias(serverTeleconferencePIN => 'serverTelephonyPIN', freeze => 1);
124              
125             __PACKAGE__->_alias(add_preload => 'preloadIds');
126              
127             =head1 METHODS
128              
129             =cut
130              
131             sub _alias {
132             my ($entity_class, $from, $to, %opt) = @_;
133              
134             $from = lcfirst($from);
135             $to = lcfirst($to);
136              
137             die 'usage: $entity_class->_alias(alias, prop, %opts)'
138             unless ($entity_class
139             && $from && !ref($from)
140             && $to && !ref($to));
141              
142             my $aliases = $entity_class->_get_aliases;
143              
144             die "$entity_class: attempted redefinition of alias: $from"
145             if $aliases->{$from};
146              
147             die "$entity_class: can't alias $from it's already a property!"
148             if $entity_class->property_types->{$from};
149              
150             die "$entity_class: attempt to alias $from to non-existant target $to - check spelling and declaration order"
151             unless $entity_class->can($to) || { $entity_class->params }->{$to};
152              
153             $opt{to} = $to;
154             $aliases->{$from} = \%opt;
155              
156             return \%opt;
157             }
158              
159             sub _data_owned_by {
160             my $class = shift;
161             my $delegate_class = shift;
162             my @props = @_;
163              
164             my %owns = (%{ $delegate_class->property_types },
165             %{ $delegate_class->_aliases },
166             $delegate_class->params);
167              
168             return grep { exists $owns{$_} } @props;
169             }
170              
171             sub set {
172             my $self = shift;
173             my %data = @_;
174              
175             my $delegates = $self->_delegates;
176              
177             foreach my $delegate (sort keys %$delegates) {
178              
179             my $delegate_class = $delegates->{$delegate};
180             my @delegate_props = $self->_data_owned_by($delegate_class => sort keys %data);
181             my %delegate_data = map {$_ => delete $data{$_}} @delegate_props;
182              
183             $self->$delegate->set( %delegate_data );
184             }
185              
186             carp 'unknown session attributes '.join(' ', sort keys %data).'. expected: '.join(' ', sort $self->properties)
187             if keys %data;
188              
189             return $self;
190             }
191              
192             sub _readback_check {
193             my ($class, $_updates_ref, $rows, @args) = @_;
194             my %updates = %$_updates_ref;
195              
196             $class->_canonicalize_properties( \%updates );
197             my $id = $updates{id};
198              
199             my $delegates = $class->_delegates;
200              
201             foreach my $delegate (sort keys %$delegates) {
202             my $delegate_class = $delegates->{$delegate};
203              
204             my %delegated_updates;
205             foreach( $class->_data_owned_by($delegate_class => %updates) ){
206             $delegated_updates{$_} = delete $updates{$_};
207             }
208              
209             $delegated_updates{meetingId} = $id if $id;
210             if ($delegate eq 'meeting') {
211             # actual start and end times can get a bit tricky for
212             # scheduled meetings. just avoid checking them for now
213             delete $delegated_updates{start};
214             delete $delegated_updates{end};
215             }
216              
217             foreach my $row (@$rows) {
218              
219             $delegate_class
220             ->_readback_check(\%delegated_updates, [$row->{$delegate}], @args);
221             }
222             }
223              
224             return $class->SUPER::_readback_check(\%updates, $rows, @args);
225             }
226              
227             sub _freeze {
228             my $class = shift;
229             my %data = %{ shift() };
230             my %opts = @_;
231              
232             my $delegates = $class->_delegates;
233              
234             my %frozen = map {
235             my $delegate = $_;
236             my $delegate_class = $delegates->{$delegate};
237             my @delegate_props = $class->_data_owned_by($delegate_class => sort keys %data);
238              
239             #
240             # accept flattened or unflattened data: eg $data{meeting}{start} or $data{start}
241              
242             my %delegate_data = (
243             %{ $data{$delegate} || {}},
244             map {$_ => delete $data{$_}} @delegate_props
245             );
246              
247             %{ $delegate_class->_freeze (\%delegate_data, canonical => 1) };
248             } (sort keys %$delegates);
249              
250             $class->_freeze_participants( \%frozen );
251             #
252             # pass any left-overs to superclass for resolution.
253             #
254             my $params_etc = $class->SUPER::_freeze(\%data);
255             foreach (sort keys %$params_etc) {
256             $frozen{$_} = $params_etc->{$_} unless defined $frozen{$_};
257             }
258              
259             $class->__apply_freeze_aliases( \%frozen )
260             unless $opts{canonical};
261              
262             return \%frozen;
263             }
264              
265             sub _freeze_participants {
266             my $class = shift;
267             my $data = shift || {};
268             #
269             # collate invited guests, moderators and regular participants
270             #
271             my $raw = delete $data->{participants};
272             my $participants = Elive::Entity::Participants->new( $raw );
273              
274             my ($guests, $moderators, $others) = $participants->tidied();
275              
276             #
277             # Note: invited guest list is ';' separated. Others are ',' separated.
278             #
279              
280             $data->{invitedGuests} = join(';', @$guests);
281             $data->{invitedModerators} = join(',', @$moderators);
282             $data->{invitedParticipantsList}= join(',', @$others);
283              
284             return $data
285             }
286              
287             sub _unpack_as_list {
288             my $class = shift;
289             my $data = shift;
290              
291             my $results_list = $class->SUPER::_unpack_as_list($data);
292              
293             die "no response received for session insert/update - check parameters?\n"
294             unless @$results_list && $results_list->[0];
295              
296             my %result ;
297             (my $meetings,
298             @result{qw{serverParameters meetingParameters participantList}})
299             = @$results_list;
300              
301             #
302             # allow for a list of repeated meetings
303             $meetings = [ $meetings ]
304             unless Elive::Util::_reftype( $meetings ) eq 'ARRAY';
305              
306             my @results = map {
307             my $meeting = $_;
308             my $meeting_id = $meeting->{MeetingAdapter}{Id};
309              
310             my $result = Elive::Util::_clone( \%result );
311              
312             foreach (keys %$result) {
313             #
314             # add the meetingId to each of the sub-records
315             my $adapter = $_.'Adapter';
316             $adapter =~ s/^(.)/uc $1/e;
317             $result->{$_}{$adapter}{MeetingId} = $meeting_id;
318             }
319              
320             $result->{meeting} = $meeting;
321             $result->{Id} = $meeting_id;
322              
323             $result;
324             } @{ $meetings };
325              
326             return \@results;
327             }
328              
329             =head2 insert
330              
331             Creates a new session on an Elluminate server, using the C command.
332              
333             use Elive;
334             use Elive::Entity::Session;
335             use Elive::Entity::Preload;
336              
337             Elive->connect('https://someEllumServer.com/my_instance',
338             'some_user', 'some_pass');
339              
340             my $session_start = time();
341             my $session_end = $session_start + 900;
342              
343             $session_start .= '000';
344             $session_end .= '000';
345              
346             my $preload = Elive::Entity::Preload->upload('c:\\Documents\intro.wbd');
347              
348             my %session_data = (
349             name => 'An example session',
350             facilitatorId => Elive->login->userId,
351             password => 'secret',
352             start => $session_start,
353             end => $session_end,
354             restricted => 1,
355             privateMeeting => 1,
356             recordingStatus => 'remote',
357             maxTalkers => 2,
358             boundaryMinutes => 15,
359             fullPermissions => 1,
360             supervised => 1,
361             seats => 10,
362             participants => [
363             -moderators => [qw(alice bob)],
364             -others => '*staff_group'
365             ],
366             add_preload => $preload,
367             );
368              
369             my $session = Elive::Entity::Session->insert( \%session_data );
370              
371             =cut
372              
373             sub insert {
374             my $class = shift;
375             my %data = %{ shift() };
376             my %opt = @_;
377              
378             my $connection = $opt{connection} || $class->connection
379             or die "not connected";
380              
381             my $participants = Elive::Entity::Participants->new( $data{participants} );
382              
383             $data{facilitatorId} ||= $connection->login->userId;
384             my $facilitatorId = $data{facilitatorId};
385              
386             $participants->add(-moderators => $facilitatorId);
387             #
388             # our freeze method breaks this down into guests, moderator and
389             # participants. Pass the participants as well, just for the readback
390             #
391             $data{participants} = $participants->tidied;
392              
393             my @objs = $class->SUPER::insert( \%data, command => 'createSession', %opt );
394             return wantarray? @objs : $objs[0];
395             }
396              
397             =head2 update
398              
399             $session->update({ boundaryTime => 15});
400              
401             # ...or...
402              
403             $session->boundaryTime(15);
404             $session->update;
405              
406             Updates session properties, using the C command.
407              
408             =cut
409              
410             sub update {
411             my $self = shift;
412             my %update_data = %{ shift() || {} };
413             my %opt = @_;
414              
415             my $changed = $opt{changed} || [$ self->is_changed];
416              
417             if (@$changed || keys %update_data) {
418             #
419             # Early ELM 3.x has a habit of wiping defaults. We're better off
420             # to rewrite the whole record
421             #
422             my @all_props = map {$_->properties} values %{$self->_delegates};
423            
424             $changed = [ grep {$_ ne 'meetingId'} @all_props ];
425              
426             my $connection = $opt{connection} || $self->connection;
427              
428             my $facilitatorId = $update_data{facilitatorId}
429             || $self->facilitatorId
430             || $connection->login->userId;
431              
432             my $participants = $update_data{participants}
433             || $self->participants;
434              
435             $participants = Elive::Entity::Participants->new( $participants )
436             unless Scalar::Util::blessed( $participants );
437              
438             $participants->add(-moderators => $facilitatorId);
439             #
440             # our freeze method breaks this down into guests, moderator and
441             # participants. Pass the participants as well, just for the readback
442             #
443             $update_data{participants} = $participants->tidied;
444              
445             return $self->SUPER::update( \%update_data, %opt, changed => $changed );
446             }
447              
448             return $self; # nothing to update
449             }
450              
451             =head2 retrieve
452              
453             Retrieves a session for the given session id.
454              
455             Elive::Entity::Session->retrieve( $session_id );
456              
457             =cut
458              
459             sub retrieve {
460             my $class = shift;
461             my $id = shift;
462             my %opt = @_;
463              
464             ($id) = @$id
465             if Elive::Util::_reftype($id) eq 'ARRAY';
466              
467             my $id_string = Elive::Util::string($id);
468             die "nothing to retrieve" unless $id_string;
469              
470             my $connection = $opt{connection} || $class->connection
471             or die "not connected";
472              
473             my $meeting = Elive::Entity::Meeting->retrieve( $id_string, %opt )
474             or return;
475              
476             my $self = $class->construct({id => $id_string}, connection => $connection);
477             $self->meeting( $meeting );
478              
479             return $self;
480             }
481              
482             =head2 list
483              
484             List all sessions that match a given criteria:
485              
486             my $sessions = Elive::Entity::Session->list( filter => "(name like '*Sample*')" );
487              
488             =cut
489              
490             sub list {
491             my $class = shift;
492             my %opt = @_;
493              
494             my $connection = $opt{connection} || $class->connection
495             or die "not connected";
496              
497             my $meetings = Elive::Entity::Meeting->list(%opt);
498              
499             my @sessions = map {
500             my $meeting = $_;
501              
502             my $self = $class->construct({id => $meeting->meetingId},
503             connection => $connection);
504              
505             $self->meeting($meeting);
506              
507             $self;
508             } @$meetings;
509              
510             return \@sessions;
511             }
512              
513             =head2 delete
514              
515             Deletes a completed or unwanted session from the Elluminate server.
516              
517             my $session = Elive::Entity::Session->retrieve( $session_id );
518             $session->delete;
519              
520             Note that a session, will have its C property immediately set to true,
521             but may remain accessible for a short period of time until garbage collected.
522              
523             So to check for a deleted session:
524              
525             my $session = Elive::Entity::Session->retrieve( $session_id );
526             my $session_is_deleted = !$session || $session->deleted;
527              
528             =cut
529              
530             sub delete {
531             my $self = shift;
532             my %opt = @_;
533              
534             $self->meeting->delete;
535             $self->_deleted(1);
536              
537             my $delegates = $self->_delegates;
538              
539             foreach my $delegate (sort keys %$delegates) {
540             # ELM cascades the delete for us
541             $self->$delegate->_deleted(1) if $self->{$delegate};
542             }
543              
544             return 1;
545             }
546              
547             =head2 is_changed
548              
549             Returns a list of properties that have unsaved changes.
550              
551             my $session = Elive::Entity::Session->retrieve( $session_id);
552             #
553             # ..then later on
554             #
555             $session->seats( $session->seats + 5);
556             @changed = $session->is_changed;
557             #
558             # @changed will contained 'seats', plus any other unsaved updates.
559             #
560              
561             Destroying an object with unsaved changes will cause a warning. To avoid this,
562             you will either need to call C on the object to save the changes, or
563             C to discard the changes.
564              
565             =cut
566              
567             sub is_changed {
568             my $self = shift;
569              
570             my $delegates = $self->_delegates;
571              
572             return map {$self->{$_}? $self->$_->is_changed: ()} (sort keys %$delegates)
573             }
574              
575             =head2 revert
576              
577             $session->revert('seats'); # revert just the 'seats' property
578             $session->revert(); # revert everything
579              
580             Reverts unsaved updates.
581              
582             =cut
583              
584             sub revert {
585             my $self = shift;
586              
587             my $delegates = $self->_delegates;
588              
589             for (sort keys %$delegates) {
590             $self->$_->revert if $self->{$_};
591             }
592              
593             return $self;
594             }
595              
596             =head1 Working with Participants
597              
598             =head2 Constructing Participant Lists
599              
600             A simple input list of participants might look like:
601              
602             @participants = (qw{alice bob *perl_prog_tut_1});
603              
604             By default, all users/groups/guest in the list are added as unprivileged regular participants.
605              
606             The list can be interspersed with C<-moderators> and C<-others> markers
607             to indicate moderators and regular users.
608              
609             @participants = (-moderators => qw(alice bob),
610             -others => '*perl_prog_tut_1');
611              
612             Each participant in the list can be one of several things:
613              
614             =over 4
615              
616             =item * A user-id string, in the format: CuserIdE>
617              
618             =item * A pre-fetched user object of type L
619              
620             =item * A group-id string, in the format: C<*EgroupIdE>
621              
622             =item * A pre-fetched group object of type L
623              
624             =item * An invited guest, in the format: C, e.g. C
625              
626             =back
627              
628             Unless you're using LDAP, you're likely to have to look-up users and groups
629             to resolve login names and group names:
630              
631             my $alice = Elive::Entity::User->get_by_loginName('alice');
632             my $bob = Elive::Entity::User->get_by_loginName('bob');
633             my $tut_group = Elive::Entity::Group->list(filter => "groupName = 'Perl Tutorial Group 1'");
634              
635             my @participants = (-moderators => [$alice, $bob],
636             -others => $tut_group,
637             );
638              
639             Then, you just need to pass the list in when you create or update the session:
640              
641             Elive::Entity::Session->create({
642             # ... other options
643             participants => \@participants
644             });
645              
646             You can also fully construct the participant list.
647              
648             use Elive::Entity::Participants;
649             my $participants_obj = Elive::Entity::Participants->new(\@participants);
650              
651             $participants_obj->add(-other => @latecomers);
652              
653             Elive::Entity::Session->create({
654             # ... other options
655             participants => $participants_obj,
656             });
657              
658             =head2 Managing Participant Lists
659              
660             Participant lists are returned an arrays of elements of type
661             L. Each participant contains one of:
662              
663             =over 4
664              
665             =item type 0 (L)
666              
667             =item type 1 (L), or
668              
669             =item type 2 (L)
670              
671             =back
672              
673             Each participant will contain either an C, C or C
674             object. For example, to print the list of participants for a session:
675              
676             my $session = Elive::Entity::Session->retrieve($session_id);
677             my $participants = $session->participants;
678              
679             foreach my $participant (@$participants) {
680              
681             if (my $user = $participant->user) {
682             my $loginName = $user->loginName;
683             my $email = $user->email;
684              
685             print 'user: '.$loginName;
686             print ' <'.$email.'>'
687             if $email;
688             }
689             elsif (my $group = $participant->group) {
690             my $id = $group->groupId;
691             my $name = $group->name;
692              
693             print 'group: *'.$id;
694             print ' <'.$name.'>'
695             if $name;
696             }
697             elsif (my $guest = $participant->guest) {
698             my $loginName = $guest->loginName;
699             my $displayName = $guest->displayName;
700              
701             print 'guest: '.$displayName;
702             print ' ('.$loginName.')'
703             if $loginName;
704             }
705             else {
706             my $type = $participant->type;
707             die "unknown participant type: $type"; # elm 4.x? ;-)
708             }
709              
710             print " [moderator]"
711             if $participant->is_moderator;
712              
713             print "\n";
714             }
715              
716             You may modify this list in any way, then update the session it belongs to:
717              
718             $participants->add( -moderators => 'trev'); # add 'trev' as a moderator
719              
720             $session->update({participants => $participants});
721              
722             =head1 Working with Preloads
723              
724             There are three types of preloads:
725              
726             =over 4
727              
728             =item * C: file extension C<*.wbd>, C<*.wbp>
729              
730             =item * C (Elluminate Plan!): file extensions: C<*.elp>, C<*.elpx>
731              
732             =item * C (Multimedia): anything else
733              
734             =back
735              
736             =head2 Creating Preloads
737              
738             Preloads may be:
739              
740             1. uploaded from a local file:
741              
742             my $preload1 = Elive::Entity::Preload->upload('c:\\Documents\slide1.wbd');
743              
744             2. uploaded from binary content:
745              
746             open ( my $fh, '<', 'c:\\Documents\slide2.wbd')
747             or die "unable to open preload file $!";
748             my $content = do {local $/; $fh->binmode; <$fh>};
749             close $fh;
750              
751             my $preload2 = Elive::Entity::Preload->upload({
752             name => 'slide2.wbd',
753             data => $content,
754             });
755              
756             3. imported from a file residing on the Elluminate Live! server:
757              
758             my $preload3 = Elive::Entity::Preload
759             ->import_from_server('/home/uploads/slide3.wbd');
760              
761             The type of the preload can be C, C or C. Each
762             preload also has a C property. Both are guessed from the file
763             extension, or you can supply, these details yourself:
764              
765             $preload2 = Elive::Entity::Preload->upload({
766             name => 'slide2.wbd',
767             data => $content,
768             type => 'whiteboard',
769             mimeType => 'application/octet-stream',
770             });
771              
772             The C method also has an extended form:
773              
774             $preload3 = Elive::Entity::Preload->import_from_server({
775             fileName =>'/home/uploads/slide3.wbd.tmp123'
776             ownerId => 357147617360,
777             type => 'whiteboard',
778             mimeType => 'application/octet-stream',
779             });
780              
781             Where C is the path to the file to be uploaded.
782              
783             =head2 Associating Preloads with Sessions
784              
785             Preloads can then be added to sessions in a number of ways:
786              
787             1. at session creation
788              
789             my $session = Elive::Entity->Session->create({
790             # ...
791             add_preload => $preload1,
792             });
793              
794             2. when updating a session
795              
796             $session->update({add_preload => $preload2});
797              
798             3. via the add_preload() method
799              
800             $session->add_preload( $preload3 );
801              
802             A single preload can be shared between sessions:
803              
804             $session1->add_preload( $preload );
805             $session2->add_preload( $preload );
806              
807             Attempting to add the same preload to a session more than once is considered
808             an error. The C method might help here.
809              
810             $session->add_preload( $preload )
811             unless $session->check_preload( $preload );
812              
813             Preloads are automatically garbage collected by ELM, if they are not
814             associated with any sessions, or the sessions have been deleted.
815              
816             Please see also L.
817              
818             =head1 Providing access to Sessions (session JNLPs)
819              
820             If a user has been registered as a meeting participant, either by being
821             directly assigned as a participant or by being a member of a group, you
822             can then create a JNLP for access to the meeting, giving either a C,
823             C, or C.
824              
825             my $user_jnlp = $session->buildJNLP(userName => $username);
826              
827             There's a slightly different format for guests:
828              
829             my $guest_jnlp = $session->buildJNLP(
830             displayName => $guest_display_name,
831             sessionRole => ${Elive::Entity::Role::MODERATOR} # 2
832             );
833              
834             Unlike registered users, guests do not need to be registered as a session
835             participant. They will be given a default role of
836             ${Elive::Entity::Role::PARTICIPANT} (3).
837              
838             For more information, please see L.
839              
840             =head1 Working with recordings (recording JNLPs)
841              
842             A session can be associated with multiple recording segments. A segment is
843             created each time recording is stopped and restarted, or when all participants
844             entirely vacate the session. This can happen multiple times for long running
845             sessions.
846              
847             The recordings seem to generally become available within a few minutes, without
848             any need to close or exit the session.
849              
850             my $recordings = $session->list_recordings;
851              
852             if (@$recordings) {
853             # build a JNLP for the first recording
854             my $recording_jnlp = $recordings[0]->buildJNLP(userId => $username);
855             }
856              
857             Also note that recordings are not deleted, when you delete sessions. You
858             may want to delete associated recordings when you delete sessions:
859              
860             my $recordings = $session->list_recordings;
861             $session->delete;
862             $_->delete for (@$recordings);
863              
864             However it is often customary to keep recordings for an extended period of
865             time - they will remain accessible from the C web page on your
866             Elluminate Live! web server.
867              
868             For more information, please see L.
869              
870             =head1 Working with Recurring Sessions
871              
872             =head2 Recurring Sessions - Parameters
873              
874             The C command has a number of additional parameters for
875             setting up blocks of recurring meetings:
876              
877             =head3 until C<(HiResDate)>
878              
879             Repeat session until this date.
880              
881             =head3 repeatEvery C<(Int)>
882              
883             Repeat session type:
884              
885             =over 4
886              
887             =item 0 no repeat,
888              
889             =item 1 -> repeat every X days (as defined by C),
890              
891             =item 2 -> Repeat every x (as defined by C) weeks for each of the select days as defined by C ... C.
892              
893             =item 3-5 -> Reserved,
894              
895             =item 6 -> Monthly session: Repeat on the X'th week on the month of the day of the as defined: 0 -> sunday, 1 -> Monday, etc.
896              
897             =back
898              
899             =head3 repeatSessionInterval C<(Int)>
900              
901             Repeat the session every X days|days depending of repeatEvery value.
902              
903             =head3 repeatSessionMonthlyInterval C<(Int)>
904              
905             Week of the month session should be scheduled in.
906              
907             =head3 repeatSessionMonthlyDay C<(Int)>
908              
909             Day of the week the monthly session should be scheduled in.
910              
911             =head3 sundaySessionIndicator ... saturdaySessionIndicator C<(Bool)>
912              
913             For C value = 2, which days sessions should be scheduled.
914              
915             =head3 timeZone C<(Str)>
916              
917             An optional alternate time-zone name to use for for the scheduling
918             calculations (E.g. C).
919              
920             =head2 Recurring Sessions - Example
921              
922             For example, the following inserts three meetings, of duration 15 minutes,
923             for today (starting in 5 minutes), tomorrow and the following day:
924              
925             use DateTime;
926              
927             my $start = DateTime->now->add(minutes => 5);
928             my $end = $start->clone->add(minutes => 15);
929             my $until = $end->clone->add(days => 2);
930              
931             my $start_msec = $start->epoch . '000';
932             my $end_msec = $end->epoch . '000';
933             my $until_msec = $until->epoch . '000';
934            
935             my %insert_data = (
936             name => 'daily scrum meeting',
937             facilitatorId => Elive->login->userId,
938             password => 'sssh!',
939             privateMeeting => 1,
940             restricted => 1,
941             participants => '*the_team',
942             start => $start_msec,
943             end => $end_msec,
944              
945             until => $until_msec,
946             repeatEvery => 1,
947             repeatSessionInterval => 1,
948             );
949              
950             my @sessions = $class->insert(\%insert_data);
951              
952             =head1 Session Property Reference
953              
954             Here's an alphabetical list of all available session properties
955              
956             =head2 adapter C<(Str)>
957              
958             This property is read-only. It should always have the value C for sessions created via L.
959              
960             =head2 allModerators C<(Bool)>
961              
962             All participants can moderate.
963              
964             =head2 boundaryMinutes C<(Int)>
965              
966             Session boundary time C<(minutes)>.
967              
968             =head2 costCenter C<(Str)>
969              
970             User defined cost center.
971              
972             =head2 deleted C<(Bool)>
973              
974             True if the session has been deleted.
975              
976             =head2 enableTelephony C<(Bool)>
977              
978             Telephony is enabled
979              
980             =head2 end C<(HiResDate)>
981              
982             The session end time C<(milliseconds)>. This can be constructed by appending
983             '000' to a unix ten digit epoch date.
984              
985             =head2 facilitatorId C<(Str)>
986              
987             The userId of the facilitator who created the session. They will
988             always have moderator access.
989              
990             =head2 followModerator C<(Bool)>
991              
992             Whiteboard slides are locked to moderator view.
993              
994             =head2 fullPermissions C<(Bool)>
995              
996             Whether participants can perform activities (e.g. use the whiteboard) before
997             the supervisor arrives.
998              
999             =head2 id C<(Int)>
1000              
1001             The sessionId (meetingId).
1002              
1003             =head2 inSessionInvitation C<(Bool)>
1004              
1005             Whether moderators can invite other individuals from within the online session
1006              
1007             =head2 maxTalkers C<(Int)>
1008              
1009             The maximum number of simultaneous talkers.
1010              
1011             =head2 moderatorNotes C<(Str)>
1012              
1013             General notes for moderators. These are not uploaded to the live session).
1014              
1015             =head2 moderatorTelephonyAddress C<(Str)>
1016              
1017             Either a PHONE number or SIP address for the moderator for telephone.
1018              
1019             =head2 moderatorTelephonyPIN C<(Str)>
1020              
1021             PIN for moderator telephony
1022              
1023             =head2 name C<(Str)>
1024              
1025             Session name.
1026              
1027             =head2 participantTelephonyAddress C<(Str)>
1028              
1029             Either a PHONE number or SIP address for the participants for telephone.
1030              
1031             =head2 participantTelephonyPIN C<(Str)>
1032              
1033             PIN for participants telephony.
1034              
1035             =head2 participants C<(Array)>
1036              
1037             A list of users, groups and invited guest that are attending the session,
1038             along with their access levels (moderator or participant). See
1039             L.
1040              
1041             =head2 password C<(Str)>
1042              
1043             A password for the session.
1044              
1045             =head2 privateMeeting C<(Str)>
1046              
1047             Whether to hide the session (meeting) from the public schedule.
1048              
1049             =head2 profile C<(Str)>
1050              
1051             Which user profiles are displayed on mouse-over: C, C
1052             (moderators only) or C.
1053              
1054             =head2 raiseHandOnEnter C<(Bool)>
1055              
1056             Raise hands automatically when users join the session.
1057              
1058             =head2 recordingObfuscation C<(Bool)>
1059              
1060             =head2 recordingResolution C<(Str)>
1061              
1062             Resolution of session recording. Options are: C:course gray,
1063             C:course color, C:medium gray, C:medium color, C:fine gray,
1064             or C:fine color
1065              
1066             =head2 recordingStatus C<(Str)>
1067              
1068             Recording status; C, C or C (start/stopped by moderator)
1069              
1070             =head2 redirectURL C<(Str)>
1071              
1072             URL to redirect users to after the online session is over.
1073              
1074             =head2 restrictedMeeting C<(Bool)>
1075              
1076             Restrict session to only invited participants.
1077              
1078             =head2 seats C<(Int)>
1079              
1080             Specify the number of seats to reserve on the server.
1081              
1082             =head2 serverTelephonyAddress C<(Str)>
1083              
1084             Either a PHONE number or SIP address for the server.
1085              
1086             =head2 serverTelephonyPIN C<(Str)>
1087              
1088             PIN for the server.
1089              
1090             =head2 start C<(HiResDate)>
1091              
1092             Session start time. This can be constructed by appending '000' to a unix ten
1093             digit epoch date.
1094              
1095             =head2 supervised C<(Bool)>
1096              
1097             Whether the moderator can see private messages.
1098              
1099             =head2 telephonyType C<(Str)>
1100              
1101             This can be either C or C.
1102              
1103             =head2 userNotes C<(Str)>
1104              
1105             General notes for users. These are not uploaded to the live session).
1106              
1107             =head2 videoWindow C<(Int)>
1108              
1109             The maximum number of cameras.
1110              
1111              
1112             =head1 BUGS AND LIMITATIONS
1113              
1114             =over 4
1115              
1116             =item * Meeting telephony is not yet supported
1117              
1118             =item * C method caveats
1119              
1120             Maintaining the L abstraction may involve fetches from
1121             several entities. This is mostly transparent, but does have some implications
1122             for the C method:
1123              
1124             =over 4
1125              
1126             =item * You can only filter on core meeting properties (C, C, C, C, C, C, C, C, C and C).
1127              
1128             =item * Access to other properties requires a secondary fetch. This is done
1129             lazily on a per record basis and may be considerably slower. This includes
1130             access to attributes of meeting parameters, server parameter and the
1131             participant list.
1132              
1133             =back
1134              
1135             =back
1136              
1137             =head1 SEE ALSO
1138              
1139             L - provides an identical interface,
1140             but implements C and C using ELM 2.x compatible commands.
1141              
1142             =cut
1143              
1144             1;