From 53dab6b97d6f8573ccca3e6e24044f27a74aee59 Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Wed, 6 Jan 2021 07:51:09 +0100 Subject: [PATCH] OO-5163: URL templates for live stream course element --- .../commons/calendar/CalendarManager.java | 1 + .../calendar/_i18n/LocalStrings_de.properties | 5 +- .../calendar/_i18n/LocalStrings_en.properties | 3 + .../manager/ICalFileCalendarManager.java | 11 +- .../commons/calendar/model/KalendarEvent.java | 9 + .../calendar/model/KalendarRecurEvent.java | 1 + .../calendar/ui/CalendarEntryForm.java | 101 ++++++++- .../nodes/livestream/LiveStreamService.java | 21 +- ...iveStreamLaunchDAO.java => LaunchDAO.java} | 2 +- .../manager/LiveStreamServiceImpl.java | 68 ++++-- .../livestream/manager/UrlTemplateDAO.java | 93 ++++++++ .../nodes/livestream/model/UrlTemplate.java | 44 ++++ .../livestream/model/UrlTemplateImpl.java | 152 +++++++++++++ .../ui/LiveStreamAdminController.java | 200 ++++++------------ .../ui/LiveStreamAdminSettingsController.java | 189 +++++++++++++++++ .../livestream/ui/UrlTemplateDataModel.java | 104 +++++++++ .../ui/UrlTemplateEditController.java | 142 +++++++++++++ .../ui/UrlTemplateListController.java | 199 +++++++++++++++++ .../nodes/livestream/ui/_content/admin.html | 9 + .../ui/_i18n/LocalStrings_de.properties | 12 ++ .../ui/_i18n/LocalStrings_en.properties | 12 ++ .../org/olat/upgrade/OLATUpgrade_14_2_0.java | 4 +- src/main/resources/META-INF/persistence.xml | 1 + .../database/mysql/alter_15_3_x_to_15_3_8.sql | 10 + .../database/mysql/setupDatabase.sql | 10 + .../oracle/alter_15_3_x_to_15_3_8.sql | 10 + .../database/oracle/setupDatabase.sql | 9 + .../postgresql/alter_15_3_x_to_15_3_8.sql | 10 + .../database/postgresql/setupDatabase.sql | 10 + ...mLaunchDAOTest.java => LaunchDAOTest.java} | 4 +- .../manager/UrlTemplateDAOTest.java | 119 +++++++++++ .../java/org/olat/test/AllTestsJunit4.java | 3 +- 32 files changed, 1401 insertions(+), 167 deletions(-) rename src/main/java/org/olat/course/nodes/livestream/manager/{LiveStreamLaunchDAO.java => LaunchDAO.java} (98%) create mode 100644 src/main/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAO.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/model/UrlTemplate.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/model/UrlTemplateImpl.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminSettingsController.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateDataModel.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateEditController.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateListController.java create mode 100644 src/main/java/org/olat/course/nodes/livestream/ui/_content/admin.html rename src/test/java/org/olat/course/nodes/livestream/manager/{LiveStreamLaunchDAOTest.java => LaunchDAOTest.java} (97%) create mode 100644 src/test/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAOTest.java diff --git a/src/main/java/org/olat/commons/calendar/CalendarManager.java b/src/main/java/org/olat/commons/calendar/CalendarManager.java index 8c5776c51d1..96489b3131b 100644 --- a/src/main/java/org/olat/commons/calendar/CalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/CalendarManager.java @@ -63,6 +63,7 @@ public interface CalendarManager { public static final String ICAL_X_OLAT_EXTERNAL_ID = "X-OLAT-EXTERNAL-ID"; public static final String ICAL_X_OLAT_EXTERNAL_SOURCE = "X-OLAT-EXTERNAL-SOURCE"; public static final String ICAL_X_OLAT_VIDEO_STREAM_URL = "X-OLAT-VIDEO-STREAM-URL"; + public static final String ICAL_X_OLAT_VIDEO_STREAM_URL_TEMPLATE_KEY = "X-OLAT-VIDEO-STREAM-URL_TEMPLATE_KEY"; /** path prefix for personal iCal feed **/ public static final String ICAL_PREFIX_AGGREGATED = "/paggregated/"; diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties index 81bb551c4c1..7359743747e 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties @@ -120,6 +120,9 @@ cal.links.submit=Speichern cal.links.title=Verkn\u00FCpfung cal.list=Kalenderliste cal.live.stream.url=URL Live Stream +cal.live.stream.url.template=URL Live Stream +cal.live.stream.url.type=Live Stream Auswahl +cal.live.stream.url.type.manually=Manuell cal.managecalendars=Kalender importieren und verwalten cal.mon=Montag cal.month=Monat @@ -187,7 +190,7 @@ delete.future=Alle zuk\u00FCnftige Termine l\u00F6schen delete.all=Alle Termine l\u00F6schen delete.one=Nur die Okkurenz l\u00F6schen error.goto.date=Falsche Datumseingabe (dd.mm.yyyy) -error.atleast.one=Mindestens ein Kalender ausw\u00e4hlen +error.atleast.one=Mindestens einen Kalender ausw\u00E4hlen menu.admin.calendar=Kalender menu.admin.calendar.alt=Kalender month.long.apr=$org.olat.core.gui.components.form.flexible.impl.elements\:month.long.apr diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties index b543b8b3e1c..1a07dc0e699 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties @@ -120,6 +120,9 @@ cal.links.submit=Save cal.links.title=Link cal.list=List of calendars cal.live.stream.url=URL live stream +cal.live.stream.url.template=URL live stream +cal.live.stream.url.type=Live stream selection +cal.live.stream.url.type.manually=Manually cal.managecalendars=Import and manage calendars cal.mon=Monday cal.month=Month diff --git a/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java index f618b0331b9..bb1e9578ee8 100644 --- a/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java @@ -701,6 +701,9 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea if(StringHelper.containsNonWhitespace(kEvent.getLiveStreamUrl())) { vEventProperties.add(new XProperty(ICAL_X_OLAT_VIDEO_STREAM_URL, kEvent.getLiveStreamUrl())); } + if (kEvent.getLiveStreamUrlTemplateKey() != null) { + vEventProperties.add(new XProperty(ICAL_X_OLAT_VIDEO_STREAM_URL_TEMPLATE_KEY, kEvent.getLiveStreamUrlTemplateKey().toString())); + } return vEvent; } @@ -889,6 +892,10 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea calEvent.setLiveStreamUrl(liveStreamUrl.getValue()); } + Property liveStreamUrlTemplateKey = event.getProperty(ICAL_X_OLAT_VIDEO_STREAM_URL_TEMPLATE_KEY); + if (liveStreamUrlTemplateKey != null) + calEvent.setLiveStreamUrlTemplateKey(Long.parseLong(liveStreamUrlTemplateKey.getValue())); + return calEvent; } @@ -1130,9 +1137,7 @@ public class ICalFileCalendarManager implements CalendarManager, InitializingBea @Override public boolean updateEventFrom(final Kalendar cal, final KalendarEvent kalendarEvent) { OLATResourceable calOres = getOresHelperFor(cal); - Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, () -> { - return updateEventAlreadyInSync(cal, kalendarEvent); - }); + Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, () -> updateEventAlreadyInSync(cal, kalendarEvent)); return updatedSuccessful.booleanValue(); } diff --git a/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java b/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java index 4112c850236..1ff720ed7fa 100644 --- a/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java +++ b/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java @@ -76,6 +76,7 @@ public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> { private String recurrenceRule; private String recurrenceExc; + private Long liveStreamUrlTemplateKey; private String liveStreamUrl; private String externalId; @@ -388,6 +389,14 @@ public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> { this.recurrenceExc = recurrenceExc; } + public Long getLiveStreamUrlTemplateKey() { + return liveStreamUrlTemplateKey; + } + + public void setLiveStreamUrlTemplateKey(Long liveStreamUrlTemplateKey) { + this.liveStreamUrlTemplateKey = liveStreamUrlTemplateKey; + } + public String getLiveStreamUrl() { return liveStreamUrl; } diff --git a/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java b/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java index 4c7846d9ea9..a4c23f677e1 100644 --- a/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java +++ b/src/main/java/org/olat/commons/calendar/model/KalendarRecurEvent.java @@ -78,6 +78,7 @@ public class KalendarRecurEvent extends KalendarEvent { setParticipants(event.getParticipants()); setRecurrenceExc(event.getRecurrenceExc()); setRecurrenceRule(event.getRecurrenceRule()); + setLiveStreamUrlTemplateKey(event.getLiveStreamUrlTemplateKey()); setLiveStreamUrl(event.getLiveStreamUrl()); setSourceNodeId(event.getSourceNodeId()); setSubject(event.getSubject()); diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java index 28d4a10eb41..ccc5873ae76 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java @@ -25,7 +25,10 @@ package org.olat.commons.calendar.ui; +import static org.olat.core.util.ArrayHelper.emptyStrings; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Iterator; @@ -41,6 +44,7 @@ import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.DateChooser; import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.SelectionElement; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; @@ -49,12 +53,15 @@ import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.util.KeyValues; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.course.nodes.livestream.LiveStreamService; +import org.olat.course.nodes.livestream.model.UrlTemplate; import org.springframework.beans.factory.annotation.Autowired; @@ -68,6 +75,8 @@ public class CalendarEntryForm extends FormBasicController { private StaticTextElement calendarName; private SingleSelection chooseCalendar; private TextElement subjectEl, descriptionEl, locationEl, liveStreamUrlEl; + private MultipleSelectionElement liveStreamUrlTypeEl; + private SingleSelection liveStreamUrlTemplateEl; private SelectionElement allDayEvent; private DateChooser begin, end; @@ -88,6 +97,8 @@ public class CalendarEntryForm extends FormBasicController { @Autowired private CalendarManager calendarManager; + @Autowired + private LiveStreamService liveStreamService; /** * Display an event for modification or to add a new event. @@ -200,7 +211,7 @@ public class CalendarEntryForm extends FormBasicController { } } - liveStreamUrlEl.setValue(kalendarEvent.getLiveStreamUrl()); + updateLiveStreamUI(kalendarEvent); } @Override @@ -277,7 +288,12 @@ public class CalendarEntryForm extends FormBasicController { event.setRecurrenceRule(rrule); } - // live Stream + // Live Stream + event.setLiveStreamUrlTemplateKey(null); + if (liveStreamUrlTemplateEl.isVisible() && liveStreamUrlTemplateEl.isEnabled() && liveStreamUrlTemplateEl.isOneSelected()) { + event.setLiveStreamUrlTemplateKey(Long.valueOf(liveStreamUrlTemplateEl.getSelectedKey())); + event.setLiveStreamUrl(getLiveStreamUrlFromSelection()); + } if (liveStreamUrlEl.isVisible() && liveStreamUrlEl.isEnabled()) { String liveStreamUrl = StringHelper.containsNonWhitespace(liveStreamUrlEl.getValue()) ? liveStreamUrlEl.getValue() @@ -395,9 +411,14 @@ public class CalendarEntryForm extends FormBasicController { recurrenceEnd.setEnabled(!managedDates); recurrenceEnd.setVisible(!chooseRecurrence.getSelectedKey().equals(RECURRENCE_NONE)); + liveStreamUrlTypeEl = uifactory.addCheckboxesHorizontal("cal.live.stream.url.type", formLayout, emptyStrings(), emptyStrings()); + liveStreamUrlTypeEl.addActionListener(FormEvent.ONCHANGE); + + liveStreamUrlTemplateEl = uifactory.addDropdownSingleselect("cal.live.stream.url.template", formLayout, emptyStrings(), emptyStrings()); + liveStreamUrlTemplateEl.addActionListener(FormEvent.ONCHANGE); + liveStreamUrlEl = uifactory.addTextElement("cal.live.stream.url", 2000, event.getLiveStreamUrl(), formLayout); - liveStreamUrlEl.setEnabled(!CalendarManagedFlag.isManaged(event, CalendarManagedFlag.liveStreamUrl)); - liveStreamUrlEl.setVisible(CalendarController.CALLER_LIVE_STREAM.equals(caller)); + updateLiveStreamUI(event); classification = uifactory.addRadiosVertical("classification", "cal.form.class", formLayout, classKeys, classValues); classification.setHelpUrlForManualPage("Calendar#_visibility"); @@ -436,6 +457,59 @@ public class CalendarEntryForm extends FormBasicController { deleteEventButton.setElementCssClass("o_sel_cal_delete"); } } + + private void updateLiveStreamUI(KalendarEvent kalendarEvent) { + boolean isLiveStream = CalendarController.CALLER_LIVE_STREAM.equals(caller); + boolean liveStreamNotManaged = !CalendarManagedFlag.isManaged(kalendarEvent, CalendarManagedFlag.liveStreamUrl); + + liveStreamUrlTypeEl.setVisible(isLiveStream && liveStreamNotManaged); + liveStreamUrlTemplateEl.setVisible(isLiveStream && liveStreamNotManaged); + liveStreamUrlEl.setVisible(isLiveStream); + liveStreamUrlEl.setEnabled(liveStreamNotManaged); + + liveStreamUrlEl.setValue(kalendarEvent.getLiveStreamUrl()); + + boolean liveStreamUrlManually = true; + if (liveStreamUrlTypeEl.isVisible()) { + List<UrlTemplate> urlTemplates = liveStreamService.getAllUrlTemplates(); + if (urlTemplates.isEmpty()) { + liveStreamUrlTypeEl.setVisible(false); + liveStreamUrlTemplateEl.setVisible(false); + return; + } + + KeyValues typeKV = new KeyValues(); + typeKV.add(KeyValues.entry("key", translate("cal.live.stream.url.type.manually"))); + KeyValues urlTemplateKV = new KeyValues(); + liveStreamUrlTypeEl.setKeysAndValues(typeKV.keys(), typeKV.values()); + + urlTemplates.forEach(urlTemplate -> urlTemplateKV.add(KeyValues.entry(urlTemplate.getKey().toString(), urlTemplate.getName()))); + urlTemplateKV.sort(KeyValues.VALUE_ASC); + liveStreamUrlTemplateEl.setKeysAndValues(urlTemplateKV.keys(), urlTemplateKV.values(), null); + liveStreamUrlTemplateEl.select(liveStreamUrlTemplateEl.getKey(0), true); + + Long urlTemplateKey = kalendarEvent.getLiveStreamUrlTemplateKey(); + if (isValidUrlTemplateKey(urlTemplateKey)) { + liveStreamUrlTemplateEl.select(urlTemplateKey.toString(), true); + liveStreamUrlManually = false; + } else if (!StringHelper.containsNonWhitespace(kalendarEvent.getLiveStreamUrl())) { + liveStreamUrlTemplateEl.select(liveStreamUrlTemplateEl.getKey(0), true); + liveStreamUrlManually = false; + } + liveStreamUrlTypeEl.select(liveStreamUrlTypeEl.getKey(0), liveStreamUrlManually); + } + updateLiveStreamUI(liveStreamUrlManually); + } + + private boolean isValidUrlTemplateKey(Long urlTemplateKey) { + return urlTemplateKey != null + && Arrays.stream(liveStreamUrlTemplateEl.getKeys()).anyMatch(key -> key.equals(urlTemplateKey.toString())); + } + + private void updateLiveStreamUI(boolean manually) { + liveStreamUrlTemplateEl.setVisible(!manually); + liveStreamUrlEl.setVisible(manually); + } @Override protected void formInnerEvent (UserRequest ureq, FormItem source, FormEvent e) { @@ -445,11 +519,28 @@ public class CalendarEntryForm extends FormBasicController { boolean allDay = allDayEvent.isSelected(0); begin.setDateChooserTimeEnabled(!allDay); end.setDateChooserTimeEnabled(!allDay); + } else if (source == liveStreamUrlTypeEl) { + boolean manually = liveStreamUrlTypeEl.isAtLeastSelected(1); + updateLiveStreamUI(manually); + } else if (source == liveStreamUrlTemplateEl) { + doSyncLiveStreamUrl(); } else if(deleteEventButton == source) { fireEvent(ureq, new Event("delete")); } } - + + private void doSyncLiveStreamUrl() { + String liveStreamUrl = getLiveStreamUrlFromSelection(); + liveStreamUrlEl.setValue(liveStreamUrl); + } + + private String getLiveStreamUrlFromSelection() { + Long key = Long.valueOf(liveStreamUrlTemplateEl.getSelectedKey()); + UrlTemplate urlTemplate = liveStreamService.getUrlTemplate(key); + String url = liveStreamService.concatUrls(urlTemplate); + return StringHelper.containsNonWhitespace(url) ? url : null; + } + @Override protected void doDispose() { // diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java index 31209f23055..514766d228e 100644 --- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java +++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java @@ -25,6 +25,7 @@ import java.util.concurrent.ScheduledExecutorService; import org.olat.core.id.Identity; import org.olat.course.nodes.cal.CourseCalendars; +import org.olat.course.nodes.livestream.model.UrlTemplate; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; @@ -64,6 +65,24 @@ public interface LiveStreamService { * @return */ Long getLaunchers(RepositoryEntryRef courseEntry, String subIdent, Date from, Date to); + + UrlTemplate createUrlTemplate(String name); + + UrlTemplate updateUrlTemplate(UrlTemplate urlTemplate); + + List<UrlTemplate> getAllUrlTemplates(); + + UrlTemplate getUrlTemplate(Long key); + + void deleteUrlTemplate(UrlTemplate urlTemplate); + + /** + * Concatenate the urls of the template + * + * @param urlTemplate + * @return + */ + String concatUrls(UrlTemplate urlTemplate); /** * Split the url to separate urls @@ -80,5 +99,5 @@ public interface LiveStreamService { * @return */ String[] getStreamingUrls(String[] urls); - + } diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java b/src/main/java/org/olat/course/nodes/livestream/manager/LaunchDAO.java similarity index 98% rename from src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java rename to src/main/java/org/olat/course/nodes/livestream/manager/LaunchDAO.java index 922a6bf5fe1..46c75b24846 100644 --- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java +++ b/src/main/java/org/olat/course/nodes/livestream/manager/LaunchDAO.java @@ -42,7 +42,7 @@ import org.springframework.stereotype.Component; * */ @Component -public class LiveStreamLaunchDAO { +public class LaunchDAO { @Autowired private DB dbInstance; diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java index 3c5839ec399..b2a8ca4c332 100644 --- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java +++ b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java @@ -49,6 +49,7 @@ import org.olat.course.nodes.livestream.LiveStreamEvent; import org.olat.course.nodes.livestream.LiveStreamModule; import org.olat.course.nodes.livestream.LiveStreamService; import org.olat.course.nodes.livestream.model.LiveStreamEventImpl; +import org.olat.course.nodes.livestream.model.UrlTemplate; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.springframework.beans.factory.DisposableBean; @@ -74,7 +75,9 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean @Autowired private CalendarManager calendarManager; @Autowired - private LiveStreamLaunchDAO launchDao; + private UrlTemplateDAO urlTemplateDao; + @Autowired + private LaunchDAO launchDao; @Override public ScheduledExecutorService getScheduler() { @@ -107,7 +110,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean cTo.add(Calendar.MINUTE, bufferBeforeMin); Date to = cTo.getTime(); - return getLiveStreamEvents(calendars, from, to); + return getLiveStreamEvents(calendars, from, to, true); } @Override @@ -121,7 +124,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean cTo.add(Calendar.MINUTE, bufferBeforeMin); Date to = cTo.getTime(); - return getLiveStreamEvents(calendars, from, to); + return getLiveStreamEvents(calendars, from, to, false); } @Override @@ -136,18 +139,16 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean cTo.add(Calendar.YEAR, 10); Date to = cTo.getTime(); - return getLiveStreamEvents(calendars, from, to).stream() + return getLiveStreamEvents(calendars, from, to, false).stream() .filter(notStartedFilter(from)) .collect(Collectors.toList()); } private Predicate<LiveStreamEvent> notStartedFilter(Date from) { - return (LiveStreamEvent e) -> { - return !e.getBegin().before(from); - }; + return (LiveStreamEvent e) -> !e.getBegin().before(from); } - private List<? extends LiveStreamEvent> getLiveStreamEvents(CourseCalendars calendars, Date from, Date to) { + private List<? extends LiveStreamEvent> getLiveStreamEvents(CourseCalendars calendars, Date from, Date to, boolean syncUrl) { List<LiveStreamEvent> liveStreamEvents = new ArrayList<>(); for (KalendarRenderWrapper cal : calendars.getCalendars()) { if(cal != null) { @@ -160,9 +161,9 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean if (isLiveStream(event)) { boolean timeOnly = !privateEventsVisible && event.getClassification() == KalendarEvent.CLASS_X_FREEBUSY; - LiveStreamEventImpl liveStreamEvent = toLiveStreamEvent(event, timeOnly); + LiveStreamEventImpl liveStreamEvent = toLiveStreamEvent(event, timeOnly, syncUrl); liveStreamEvents.add(liveStreamEvent); - }; + } } } } @@ -174,7 +175,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean return event.getLiveStreamUrl() != null; } - private LiveStreamEventImpl toLiveStreamEvent(KalendarEvent event, boolean timeOnly) { + private LiveStreamEventImpl toLiveStreamEvent(KalendarEvent event, boolean timeOnly, boolean syncUrl) { LiveStreamEventImpl liveStreamEvent = new LiveStreamEventImpl(); liveStreamEvent.setId(event.getID()); liveStreamEvent.setAllDayEvent(event.isAllDayEvent()); @@ -182,6 +183,14 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean Date end = CalendarUtils.endOf(event); liveStreamEvent.setEnd(end); liveStreamEvent.setLiveStreamUrl(event.getLiveStreamUrl()); + if (syncUrl && event.getLiveStreamUrlTemplateKey() != null) { + Long key = Long.valueOf(event.getLiveStreamUrlTemplateKey()); + UrlTemplate urlTemplate = urlTemplateDao.loadByKey(key); + String concatUrls = concatUrls(urlTemplate); + if (StringHelper.containsNonWhitespace(concatUrls)) { + liveStreamEvent.setLiveStreamUrl(concatUrls); + } + } if (!timeOnly) { liveStreamEvent.setSubject(event.getSubject()); liveStreamEvent.setDescription(event.getDescription()); @@ -200,6 +209,41 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean return launchDao.getLaunchers(courseEntry, subIdent, from, to); } + @Override + public UrlTemplate createUrlTemplate(String name) { + return urlTemplateDao.create(name); + } + + @Override + public UrlTemplate updateUrlTemplate(UrlTemplate urlTemplate) { + return urlTemplateDao.update(urlTemplate); + } + + @Override + public List<UrlTemplate> getAllUrlTemplates() { + return urlTemplateDao.loadAll(); + } + + @Override + public UrlTemplate getUrlTemplate(Long key) { + return urlTemplateDao.loadByKey(key); + } + + @Override + public void deleteUrlTemplate(UrlTemplate urlTemplate) { + urlTemplateDao.delete(urlTemplate); + } + + @Override + public String concatUrls(UrlTemplate urlTemplate) { + if (urlTemplate == null) return null; + + String urls = List.of(urlTemplate.getUrl1(), urlTemplate.getUrl2()).stream() + .filter(StringHelper::containsNonWhitespace) + .collect(Collectors.joining(liveStreamModule.getUrlSeparator())); + return StringHelper.containsNonWhitespace(urls)? urls: null; + } + @Override public String[] splitUrl(String url) { if (!StringHelper.containsNonWhitespace(url)) return new String[0]; @@ -239,7 +283,7 @@ public class LiveStreamServiceImpl implements LiveStreamService, DisposableBean } } } catch(Exception e) { - log.error("", e); + log.debug("LiveStream not available.", e); } return false; diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAO.java b/src/main/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAO.java new file mode 100644 index 00000000000..2fe26760e75 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAO.java @@ -0,0 +1,93 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.manager; + +import java.util.Date; +import java.util.List; + +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.persistence.QueryBuilder; +import org.olat.course.nodes.livestream.model.UrlTemplate; +import org.olat.course.nodes.livestream.model.UrlTemplateImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * Initial date: 4 Jan 2021<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Component +public class UrlTemplateDAO { + + @Autowired + private DB dbInstance; + + public UrlTemplate create(String name) { + UrlTemplateImpl urlTemplate = new UrlTemplateImpl(); + urlTemplate.setCreationDate(new Date()); + urlTemplate.setLastModified(urlTemplate.getCreationDate()); + urlTemplate.setName(name); + dbInstance.getCurrentEntityManager().persist(urlTemplate); + return urlTemplate; + } + + public UrlTemplate update(UrlTemplate urlTemplate) { + if (urlTemplate instanceof UrlTemplateImpl) { + UrlTemplateImpl impl = (UrlTemplateImpl)urlTemplate; + impl.setLastModified(new Date()); + dbInstance.getCurrentEntityManager().merge(impl); + } + return urlTemplate; + } + + public List<UrlTemplate> loadAll() { + String query = "select urlTemplate from livestreamurltemplate urlTemplate"; + return dbInstance.getCurrentEntityManager() + .createQuery(query, UrlTemplate.class) + .getResultList(); + } + + public UrlTemplate loadByKey(Long key) { + QueryBuilder sb = new QueryBuilder(); + sb.append("select urlTemplate"); + sb.append(" from livestreamurltemplate urlTemplate"); + sb.and().append("urlTemplate.key = :key"); + + List<UrlTemplate> urlTemplates = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), UrlTemplate.class) + .setParameter("key", key) + .getResultList(); + return !urlTemplates.isEmpty()? urlTemplates.get(0): null; + } + + public void delete(UrlTemplate urlTemplate) { + QueryBuilder sb = new QueryBuilder(); + sb.append("delete from livestreamurltemplate urlTemplate"); + sb.and().append("urlTemplate.key = :key"); + + dbInstance.getCurrentEntityManager() + .createQuery(sb.toString()) + .setParameter("key", urlTemplate.getKey()) + .executeUpdate(); + } + +} diff --git a/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplate.java b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplate.java new file mode 100644 index 00000000000..e597a1c659d --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplate.java @@ -0,0 +1,44 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.model; + +/** + * + * Initial date: 4 Jan 2021<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public interface UrlTemplate { + + Long getKey(); + + String getName(); + + void setName(String name); + + String getUrl1(); + + void setUrl1(String url1); + + String getUrl2(); + + void setUrl2(String url2); + +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplateImpl.java b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplateImpl.java new file mode 100644 index 00000000000..c6ab358c173 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/model/UrlTemplateImpl.java @@ -0,0 +1,152 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.olat.core.id.CreateInfo; +import org.olat.core.id.ModifiedInfo; +import org.olat.core.id.Persistable; + +/** + * + * Initial date: 4 Jan 2021<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Entity(name="livestreamurltemplate") +@Table(name="o_livestream_url_template") +public class UrlTemplateImpl implements UrlTemplate, Persistable, CreateInfo, ModifiedInfo { + + private static final long serialVersionUID = -8987169701865579049L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) + private Date lastModified; + + @Column(name="l_name", nullable=false, insertable=true, updatable=true) + private String name; + @Column(name="l_url1", nullable=true, insertable=true, updatable=true) + private String url1; + @Column(name="l_url2", nullable=true, insertable=true, updatable=true) + private String url2; + + @Override + public Long getKey() { + return key; + } + + @Override + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public Date getLastModified() { + return lastModified; + } + + @Override + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getUrl1() { + return url1; + } + + @Override + public void setUrl1(String url1) { + this.url1 = url1; + } + + @Override + public String getUrl2() { + return url2; + } + + @Override + public void setUrl2(String url2) { + this.url2 = url2; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UrlTemplateImpl other = (UrlTemplateImpl) obj; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + return true; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } + +} diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java index 9e61db4752d..362d25bf8c0 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminController.java @@ -19,168 +19,90 @@ */ package org.olat.course.nodes.livestream.ui; -import static org.olat.core.gui.components.util.KeyValues.entry; -import static org.olat.core.gui.translator.TranslatorHelper.translateAll; -import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateInteger; -import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateMandatory; - -import java.util.Arrays; - import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItem; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; -import org.olat.core.gui.components.form.flexible.elements.SingleSelection; -import org.olat.core.gui.components.form.flexible.elements.TextElement; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -import org.olat.core.gui.components.form.flexible.impl.FormEvent; -import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.util.KeyValues; -import org.olat.core.gui.control.Controller; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.segmentedview.SegmentViewComponent; +import org.olat.core.gui.components.segmentedview.SegmentViewEvent; +import org.olat.core.gui.components.segmentedview.SegmentViewFactory; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; -import org.olat.course.nodes.livestream.LiveStreamModule; -import org.olat.course.nodes.livestream.paella.PlayerProfile; -import org.springframework.beans.factory.annotation.Autowired; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.util.resource.OresHelper; /** * - * Initial date: 5 Jun 2019<br> + * Initial date: 4 Jan 2021<br> * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com * */ -public class LiveStreamAdminController extends FormBasicController { +public class LiveStreamAdminController extends BasicController { - private static final String[] ENABLED_KEYS = new String[]{"on"}; + private static final String SETTINGS_RES_TYPE = "Settings"; + private static final String URL_TEMPLATES_RES_TYPE = "UrlTemplates"; - private MultipleSelectionElement enabledEl; - private MultipleSelectionElement multiStreamEnabledEl; - private TextElement urlSeparatorEl; - private TextElement bufferBeforeMinEl; - private TextElement bufferAfterMinEl; - private MultipleSelectionElement coachCanEditEl; - private SingleSelection playerProfileEl; + private VelocityContainer mainVC; + private SegmentViewComponent segmentView; + private Link settingsLink; + private Link urlTemplatesLink; - @Autowired - private LiveStreamModule liveStreamModule; + private LiveStreamAdminSettingsController settingsCtrl; + private UrlTemplateListController urlTemplatesCtrl; public LiveStreamAdminController(UserRequest ureq, WindowControl wControl) { - super(ureq, wControl, LAYOUT_BAREBONE); - initForm(ureq); - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator()); - generalCont.setFormTitle(translate("admin.general.title")); - generalCont.setRootForm(mainForm); - formLayout.add("genearl", generalCont); - - enabledEl = uifactory.addCheckboxesHorizontal("admin.module.enabled", generalCont, ENABLED_KEYS, - translateAll(getTranslator(), ENABLED_KEYS)); - enabledEl.select(ENABLED_KEYS[0], liveStreamModule.isEnabled()); - - multiStreamEnabledEl = uifactory.addCheckboxesHorizontal("admin.multi.stream.enabled", generalCont, - ENABLED_KEYS, translateAll(getTranslator(), ENABLED_KEYS)); - multiStreamEnabledEl.addActionListener(FormEvent.ONCHANGE); - multiStreamEnabledEl.select(ENABLED_KEYS[0], liveStreamModule.isMultiStreamEnabled()); - - urlSeparatorEl = uifactory.addTextElement("admin.url.separator", 10, liveStreamModule.getUrlSeparator(), generalCont); - urlSeparatorEl.setMandatory(true); - urlSeparatorEl.setHelpTextKey("admin.url.separator.help", null); - - FormLayoutContainer defaultValuesCont = FormLayoutContainer.createDefaultFormLayout("default_values", getTranslator()); - defaultValuesCont.setFormTitle(translate("admin.default.values.title")); - defaultValuesCont.setFormDescription(translate("admin.default.values.desc")); - defaultValuesCont.setRootForm(mainForm); - formLayout.add("defaultValues", defaultValuesCont); - - int bufferBeforeMin = liveStreamModule.getBufferBeforeMin(); - bufferBeforeMinEl = uifactory.addTextElement("admin.buffer.before.min", 4, String.valueOf(bufferBeforeMin), - defaultValuesCont); - bufferBeforeMinEl.setMandatory(true); - - int bufferAfterMin = liveStreamModule.getBufferAfterMin(); - bufferAfterMinEl = uifactory.addTextElement("admin.buffer.after.min", 4, String.valueOf(bufferAfterMin), - defaultValuesCont); - bufferAfterMinEl.setMandatory(true); - - coachCanEditEl = uifactory.addCheckboxesHorizontal("admin.coach.edit", defaultValuesCont, ENABLED_KEYS, - translateAll(getTranslator(), ENABLED_KEYS)); - boolean coachCanEdit = liveStreamModule.isEditCoach(); - coachCanEditEl.select(ENABLED_KEYS[0], coachCanEdit); + super(ureq, wControl); + mainVC = createVelocityContainer("admin"); - KeyValues playerProfileKV = new KeyValues(); - for (PlayerProfile playerProfile : PlayerProfile.values()) { - playerProfileKV.add(entry(playerProfile.name(), translate(playerProfile.getI18nKey()))); - } - playerProfileEl = uifactory.addDropdownSingleselect("admin.player.profile", defaultValuesCont, playerProfileKV.keys(), - playerProfileKV.values()); - String playerProfile = liveStreamModule.getPlayerProfile(); - if (Arrays.asList(playerProfileEl.getKeys()).contains(playerProfile)) { - playerProfileEl.select(playerProfile, true); - } - - FormLayoutContainer buttonsCont = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator()); - buttonsCont.setRootForm(mainForm); - formLayout.add("buttons", buttonsCont); - uifactory.addFormSubmitButton("save", buttonsCont); - - updateUI(); - } - - private void updateUI() { - boolean multiStream = multiStreamEnabledEl.isAtLeastSelected(1); - urlSeparatorEl.setVisible(multiStream); - playerProfileEl.setVisible(multiStream); + segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this); + settingsLink = LinkFactory.createLink("admin.settings", mainVC, this); + segmentView.addSegment(settingsLink, true); + urlTemplatesLink = LinkFactory.createLink("admin.url.templates", mainVC, this); + segmentView.addSegment(urlTemplatesLink, false); + + doOpenSettings(ureq); + putInitialPanel(mainVC); } @Override - protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if (source == multiStreamEnabledEl) { - updateUI(); + protected void event(UserRequest ureq, Component source, Event event) { + if (source == segmentView) { + if(event instanceof SegmentViewEvent) { + SegmentViewEvent sve = (SegmentViewEvent)event; + String segmentCName = sve.getComponentName(); + Component clickedLink = mainVC.getComponent(segmentCName); + if (clickedLink == settingsLink) { + doOpenSettings(ureq); + } else if (clickedLink == urlTemplatesLink){ + doOpenUrlTemplates(ureq); + } + } } - super.formInnerEvent(ureq, source, event); } - - @Override - protected boolean validateFormLogic(UserRequest ureq) { - boolean allOk = super.validateFormLogic(ureq); - - allOk &= validateMandatory(urlSeparatorEl); - allOk &= validateInteger(bufferBeforeMinEl, true); - allOk &= validateInteger(bufferAfterMinEl, true); - - return allOk; - } - - @Override - protected void formOK(UserRequest ureq) { - boolean enabled = enabledEl.isAtLeastSelected(1); - liveStreamModule.setEnabled(enabled); - - boolean multiStreamEnabled = multiStreamEnabledEl.isAtLeastSelected(1); - liveStreamModule.setMultiStreamEnabled(multiStreamEnabled); - - if (urlSeparatorEl.isVisible()) { - String urlSeparator = urlSeparatorEl.getValue(); - liveStreamModule.setUrlSeparator(urlSeparator); + + private void doOpenSettings(UserRequest ureq) { + if (settingsCtrl != null) { + removeAsListenerAndDispose(settingsCtrl); } - int bufferBeforeMin = Integer.parseInt(bufferBeforeMinEl.getValue()); - liveStreamModule.setBufferBeforeMin(bufferBeforeMin); - - int bufferAfterMin = Integer.parseInt(bufferAfterMinEl.getValue()); - liveStreamModule.setBufferAfterMin(bufferAfterMin); - - boolean coachCanEdit = coachCanEditEl.isAtLeastSelected(1); - liveStreamModule.setEditCoach(coachCanEdit); - - if (playerProfileEl.isVisible()) { - String playerProfile = playerProfileEl.getSelectedKey(); - liveStreamModule.setPlayerProfile(playerProfile); - } + WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(SETTINGS_RES_TYPE), null); + settingsCtrl = new LiveStreamAdminSettingsController(ureq, swControl); + listenTo(settingsCtrl); + mainVC.put("segmentCmp", settingsCtrl.getInitialComponent()); } + private void doOpenUrlTemplates(UserRequest ureq) { + if (urlTemplatesCtrl != null) { + removeAsListenerAndDispose(urlTemplatesCtrl); + } + + WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(URL_TEMPLATES_RES_TYPE), null); + urlTemplatesCtrl = new UrlTemplateListController(ureq, swControl); + listenTo(urlTemplatesCtrl); + mainVC.put("segmentCmp", urlTemplatesCtrl.getInitialComponent()); + } + @Override protected void doDispose() { // diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminSettingsController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminSettingsController.java new file mode 100644 index 00000000000..2fcadeaf6ff --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamAdminSettingsController.java @@ -0,0 +1,189 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.ui; + +import static org.olat.core.gui.components.util.KeyValues.entry; +import static org.olat.core.gui.translator.TranslatorHelper.translateAll; +import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateInteger; +import static org.olat.course.nodes.livestream.ui.LiveStreamUIFactory.validateMandatory; + +import java.util.Arrays; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.util.KeyValues; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.nodes.livestream.LiveStreamModule; +import org.olat.course.nodes.livestream.paella.PlayerProfile; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 5 Jun 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class LiveStreamAdminSettingsController extends FormBasicController { + + private static final String[] ENABLED_KEYS = new String[]{"on"}; + + private MultipleSelectionElement enabledEl; + private MultipleSelectionElement multiStreamEnabledEl; + private TextElement urlSeparatorEl; + private TextElement bufferBeforeMinEl; + private TextElement bufferAfterMinEl; + private MultipleSelectionElement coachCanEditEl; + private SingleSelection playerProfileEl; + + @Autowired + private LiveStreamModule liveStreamModule; + + public LiveStreamAdminSettingsController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, LAYOUT_BAREBONE); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator()); + generalCont.setFormTitle(translate("admin.general.title")); + generalCont.setRootForm(mainForm); + formLayout.add("genearl", generalCont); + + enabledEl = uifactory.addCheckboxesHorizontal("admin.module.enabled", generalCont, ENABLED_KEYS, + translateAll(getTranslator(), ENABLED_KEYS)); + enabledEl.select(ENABLED_KEYS[0], liveStreamModule.isEnabled()); + + multiStreamEnabledEl = uifactory.addCheckboxesHorizontal("admin.multi.stream.enabled", generalCont, + ENABLED_KEYS, translateAll(getTranslator(), ENABLED_KEYS)); + multiStreamEnabledEl.addActionListener(FormEvent.ONCHANGE); + multiStreamEnabledEl.select(ENABLED_KEYS[0], liveStreamModule.isMultiStreamEnabled()); + + urlSeparatorEl = uifactory.addTextElement("admin.url.separator", 10, liveStreamModule.getUrlSeparator(), generalCont); + urlSeparatorEl.setMandatory(true); + urlSeparatorEl.setHelpTextKey("admin.url.separator.help", null); + + FormLayoutContainer defaultValuesCont = FormLayoutContainer.createDefaultFormLayout("default_values", getTranslator()); + defaultValuesCont.setFormTitle(translate("admin.default.values.title")); + defaultValuesCont.setFormDescription(translate("admin.default.values.desc")); + defaultValuesCont.setRootForm(mainForm); + formLayout.add("defaultValues", defaultValuesCont); + + int bufferBeforeMin = liveStreamModule.getBufferBeforeMin(); + bufferBeforeMinEl = uifactory.addTextElement("admin.buffer.before.min", 4, String.valueOf(bufferBeforeMin), + defaultValuesCont); + bufferBeforeMinEl.setMandatory(true); + + int bufferAfterMin = liveStreamModule.getBufferAfterMin(); + bufferAfterMinEl = uifactory.addTextElement("admin.buffer.after.min", 4, String.valueOf(bufferAfterMin), + defaultValuesCont); + bufferAfterMinEl.setMandatory(true); + + coachCanEditEl = uifactory.addCheckboxesHorizontal("admin.coach.edit", defaultValuesCont, ENABLED_KEYS, + translateAll(getTranslator(), ENABLED_KEYS)); + boolean coachCanEdit = liveStreamModule.isEditCoach(); + coachCanEditEl.select(ENABLED_KEYS[0], coachCanEdit); + + KeyValues playerProfileKV = new KeyValues(); + for (PlayerProfile playerProfile : PlayerProfile.values()) { + playerProfileKV.add(entry(playerProfile.name(), translate(playerProfile.getI18nKey()))); + } + playerProfileEl = uifactory.addDropdownSingleselect("admin.player.profile", defaultValuesCont, playerProfileKV.keys(), + playerProfileKV.values()); + String playerProfile = liveStreamModule.getPlayerProfile(); + if (Arrays.asList(playerProfileEl.getKeys()).contains(playerProfile)) { + playerProfileEl.select(playerProfile, true); + } + + FormLayoutContainer buttonsCont = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator()); + buttonsCont.setRootForm(mainForm); + formLayout.add("buttons", buttonsCont); + uifactory.addFormSubmitButton("save", buttonsCont); + + updateUI(); + } + + private void updateUI() { + boolean multiStream = multiStreamEnabledEl.isAtLeastSelected(1); + urlSeparatorEl.setVisible(multiStream); + playerProfileEl.setVisible(multiStream); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == multiStreamEnabledEl) { + updateUI(); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = super.validateFormLogic(ureq); + + allOk &= validateMandatory(urlSeparatorEl); + allOk &= validateInteger(bufferBeforeMinEl, true); + allOk &= validateInteger(bufferAfterMinEl, true); + + return allOk; + } + + @Override + protected void formOK(UserRequest ureq) { + boolean enabled = enabledEl.isAtLeastSelected(1); + liveStreamModule.setEnabled(enabled); + + boolean multiStreamEnabled = multiStreamEnabledEl.isAtLeastSelected(1); + liveStreamModule.setMultiStreamEnabled(multiStreamEnabled); + + if (urlSeparatorEl.isVisible()) { + String urlSeparator = urlSeparatorEl.getValue(); + liveStreamModule.setUrlSeparator(urlSeparator); + } + + int bufferBeforeMin = Integer.parseInt(bufferBeforeMinEl.getValue()); + liveStreamModule.setBufferBeforeMin(bufferBeforeMin); + + int bufferAfterMin = Integer.parseInt(bufferAfterMinEl.getValue()); + liveStreamModule.setBufferAfterMin(bufferAfterMin); + + boolean coachCanEdit = coachCanEditEl.isAtLeastSelected(1); + liveStreamModule.setEditCoach(coachCanEdit); + + if (playerProfileEl.isVisible()) { + String playerProfile = playerProfileEl.getSelectedKey(); + liveStreamModule.setPlayerProfile(playerProfile); + } + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateDataModel.java b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateDataModel.java new file mode 100644 index 00000000000..10507b7ec85 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateDataModel.java @@ -0,0 +1,104 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.ui; + +import java.util.List; +import java.util.Locale; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; +import org.olat.course.nodes.livestream.model.UrlTemplate; + +/** + * + * Initial date: 4 Jan 2021<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class UrlTemplateDataModel extends DefaultFlexiTableDataModel<UrlTemplate> +implements SortableFlexiTableDataModel<UrlTemplate> { + + private final Locale locale; + + public UrlTemplateDataModel(FlexiTableColumnModel columnsModel, Locale locale) { + super(columnsModel); + this.locale = locale; + } + + @Override + public void sort(SortKey orderBy) { + List<UrlTemplate> rows = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort(); + super.setObjects(rows); + } + + @Override + public Object getValueAt(int row, int col) { + UrlTemplate reason = getObject(row); + return getValueAt(reason, col); + } + + @Override + public Object getValueAt(UrlTemplate row, int col) { + switch(UrlTemplateCols.values()[col]) { + case id: return row.getKey(); + case name: return row.getName(); + case url1: return row.getUrl1(); + case url2: return row.getUrl2(); + default: return null; + } + } + + @Override + public DefaultFlexiTableDataModel<UrlTemplate> createCopyWithEmptyList() { + return new UrlTemplateDataModel(getTableColumnModel(), locale); + } + + public enum UrlTemplateCols implements FlexiSortableColumnDef { + id("url.template.id"), + name("url.template.name"), + url1("url.template.url1"), + url2("url.template.url2"); + + private final String i18nKey; + + private UrlTemplateCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + @Override + public String i18nHeaderKey() { + return i18nKey; + } + + @Override + public boolean sortable() { + return true; + } + + @Override + public String sortKey() { + return name(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateEditController.java b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateEditController.java new file mode 100644 index 00000000000..9d0ce9e3bc8 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateEditController.java @@ -0,0 +1,142 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.ui; + +import java.net.URL; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.course.nodes.livestream.LiveStreamService; +import org.olat.course.nodes.livestream.model.UrlTemplate; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 4 Jan 2021<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class UrlTemplateEditController extends FormBasicController { + + private TextElement nameEl; + private TextElement url1El; + private TextElement url2El; + + private UrlTemplate urlTemplate; + + @Autowired + private LiveStreamService liveStreamService; + + public UrlTemplateEditController(UserRequest ureq, WindowControl wControl, UrlTemplate urlTemplate) { + super(ureq, wControl); + this.urlTemplate = urlTemplate; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + String name = urlTemplate != null? urlTemplate.getName(): null; + nameEl = uifactory.addTextElement("url.template.name", 138, name, formLayout); + nameEl.setMandatory(true); + + String url1 = urlTemplate != null? urlTemplate.getUrl1(): null; + url1El = uifactory.addTextElement("url.template.url1", 2000, url1, formLayout); + + String url2 = urlTemplate != null? urlTemplate.getUrl2(): null; + url2El = uifactory.addTextElement("url.template.url2", 2000, url2, formLayout); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add("buttons", buttonLayout); + uifactory.addFormSubmitButton("save", buttonLayout); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = super.validateFormLogic(ureq); + + nameEl.clearError(); + if (!StringHelper.containsNonWhitespace(nameEl.getValue())) { + nameEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + url1El.clearError(); + url2El.clearError(); + if (!StringHelper.containsNonWhitespace(url1El.getValue()) && !StringHelper.containsNonWhitespace(url2El.getValue())) { + url1El.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } else { + allOk &= validateUrl(url1El); + allOk &= validateUrl(url2El); + } + + return allOk; + } + + private boolean validateUrl(TextElement textEl) { + boolean allOk = true; + + if (StringHelper.containsNonWhitespace(textEl.getValue())) { + try { + new URL(textEl.getValue()).toURI(); + } catch(Exception e) { + textEl.setErrorKey("error.url.not.valid", null); + allOk &= false; + } + } + + return allOk; + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + @Override + protected void formOK(UserRequest ureq) { + if (urlTemplate == null) { + urlTemplate = liveStreamService.createUrlTemplate(nameEl.getValue()); + } + + urlTemplate.setName(nameEl.getValue()); + urlTemplate.setUrl1(url1El.getValue()); + urlTemplate.setUrl2(url2El.getValue()); + + urlTemplate = liveStreamService.updateUrlTemplate(urlTemplate); + + fireEvent(ureq, FormEvent.DONE_EVENT); + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateListController.java b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateListController.java new file mode 100644 index 00000000000..57b7d6732e2 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/UrlTemplateListController.java @@ -0,0 +1,199 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.ui; + +import java.util.Comparator; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.course.nodes.livestream.LiveStreamService; +import org.olat.course.nodes.livestream.model.UrlTemplate; +import org.olat.course.nodes.livestream.ui.UrlTemplateDataModel.UrlTemplateCols; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 4 Jan 2021<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class UrlTemplateListController extends FormBasicController { + + private static final String CMD_EDIT = "edit"; + private static final String CMD_DELETE = "delete"; + + private static final Comparator<UrlTemplate> NAME_ASC = (t1, t2) -> t1.getName().compareToIgnoreCase(t2.getName()); + + private FormLink addUrlTemplateButton; + private FlexiTableElement tableEl; + private UrlTemplateDataModel dataModel; + + private CloseableModalController cmc; + private UrlTemplateEditController editUrlTemplateCtrl; + private DialogBoxController deleteDialogCtrl; + + @Autowired + private LiveStreamService liveStreamService; + + public UrlTemplateListController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, LAYOUT_BAREBONE); + initForm(ureq); + loadModel(); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FormLayoutContainer buttonsTopCont = FormLayoutContainer.createButtonLayout("buttons.top", getTranslator()); + buttonsTopCont.setElementCssClass("o_button_group o_button_group_right"); + buttonsTopCont.setRootForm(mainForm); + formLayout.add(buttonsTopCont); + + addUrlTemplateButton = uifactory.addFormLink("url.template.add", buttonsTopCont, Link.BUTTON); + addUrlTemplateButton.setIconLeftCSS("o_icon o_icon-lg o_icon_add"); + + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, UrlTemplateCols.id)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UrlTemplateCols.name)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UrlTemplateCols.url1)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(UrlTemplateCols.url2)); + DefaultFlexiColumnModel editColumn = new DefaultFlexiColumnModel("url.template.edit", -1, CMD_EDIT, + new StaticFlexiCellRenderer(translate("url.template.edit"), CMD_EDIT, "", "", null)); + editColumn.setExportable(false); + columnsModel.addFlexiColumnModel(editColumn); + DefaultFlexiColumnModel deleteColumn = new DefaultFlexiColumnModel("url.template.delete", -1, CMD_DELETE, + new StaticFlexiCellRenderer(translate("url.template.delete"), CMD_DELETE, "", "", null)); + deleteColumn.setExportable(false); + columnsModel.addFlexiColumnModel(deleteColumn); + + dataModel = new UrlTemplateDataModel(columnsModel, getLocale()); + tableEl = uifactory.addTableElement(getWindowControl(), "table", dataModel, 20, false, getTranslator(), formLayout); + tableEl.setExportEnabled(true); + tableEl.setAndLoadPersistedPreferences(ureq, "livestream-url-templates"); + } + + private void loadModel() { + List<UrlTemplate> urlTemplates = liveStreamService.getAllUrlTemplates(); + urlTemplates.sort(NAME_ASC); + dataModel.setObjects(urlTemplates); + tableEl.reset(false, false, true); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == addUrlTemplateButton) { + doAddUrlTemplate(ureq); + } else if(tableEl == source) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + String cmd = se.getCommand(); + UrlTemplate urlTemplate = dataModel.getObject(se.getIndex()); + if (CMD_EDIT.equals(cmd)) { + doEditUrlTemplate(ureq, urlTemplate); + } else if (CMD_DELETE.equals(cmd)) { + doConfirmDeletUrlTemplate(ureq, urlTemplate); + } + } + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(editUrlTemplateCtrl == source) { + if(event == Event.DONE_EVENT) { + loadModel(); + } + cmc.deactivate(); + cleanUp(); + } else if(cmc == source) { + cleanUp(); + } else if (deleteDialogCtrl == source) { + if (DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) { + UrlTemplate urlTemplate = (UrlTemplate)deleteDialogCtrl.getUserObject(); + liveStreamService.deleteUrlTemplate(urlTemplate); + loadModel(); + } + } + super.event(ureq, source, event); + } + + private void cleanUp() { + removeAsListenerAndDispose(editUrlTemplateCtrl); + removeAsListenerAndDispose(cmc); + cmc = null; + } + + private void doAddUrlTemplate(UserRequest ureq) { + editUrlTemplateCtrl = new UrlTemplateEditController(ureq, getWindowControl(), null); + listenTo(editUrlTemplateCtrl); + + String title = translate("url.template.add"); + cmc = new CloseableModalController(getWindowControl(), "close", editUrlTemplateCtrl.getInitialComponent(), true, title, true); + listenTo(cmc); + cmc.activate(); + } + + private void doEditUrlTemplate(UserRequest ureq, UrlTemplate urlTemplate) { + editUrlTemplateCtrl = new UrlTemplateEditController(ureq, getWindowControl(), urlTemplate); + listenTo(editUrlTemplateCtrl); + + String title = translate("url.template.edit.title"); + cmc = new CloseableModalController(getWindowControl(), "close", editUrlTemplateCtrl.getInitialComponent(), true, title, true); + listenTo(cmc); + cmc.activate(); + } + + private void doConfirmDeletUrlTemplate(UserRequest ureq, UrlTemplate urlTemplate) { + String text = translate("url.template.delete.confirm", new String[] { urlTemplate.getName() }); + deleteDialogCtrl = activateYesNoDialog(ureq, translate("url.template.delete"), text, deleteDialogCtrl); + deleteDialogCtrl.setUserObject(urlTemplate); + } + + @Override + protected void formOK(UserRequest ureq) { + // + + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_content/admin.html b/src/main/java/org/olat/course/nodes/livestream/ui/_content/admin.html new file mode 100644 index 00000000000..2dd819318ee --- /dev/null +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_content/admin.html @@ -0,0 +1,9 @@ +<div class="clearfix"> + #if($r.visible("segments")) + $r.render("segments")<br/> + #end + + #if($r.available("segmentCmp")) + $r.render("segmentCmp") + #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties index 142f7deee22..554802e8c5f 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_de.properties @@ -9,12 +9,15 @@ admin.menu.title.alt=$:\admin.menu.title admin.module.enabled=Kursbaustein admin.multi.stream.enabled=Multistream admin.player.profile=Stream +admin.settings=Einstellungen admin.url.separator=URL Trennzeichen admin.url.separator.help=Trennzeichen, um mehrere URLs eines einzelnen Termins zu trennen. +admin.url.templates=URL Vorlagen config.buffer.before.min=Vorlaufzeit (in Minuten) config.buffer.after.min=Nachlaufzeit (in Minuten) config.coach.edit=Betreuer darf Termine bearbeiten config.player.profile=$\:admin.player.profile +error.url.not.valid=URL nicht g\u00FCltig form.error.wrong.int=Falsches Zahlenformat. Beispiele\: 2, 10, 144 link.text=Video Livestream list.title=Anstehende Livestreams @@ -39,6 +42,15 @@ table.header.end=$org.olat.commons.calendar\:cal.form.end table.header.location=$org.olat.commons.calendar\:cal.form.location table.header.subject=$org.olat.commons.calendar\:cal.form.subject table.header.viewers=Zuschauer +url.template.add=URL Vorlage erstellen +url.template.delete=L\u00F6schen +url.template.delete.confirm=Wollen die die URL Vorlage "{0}" wirklich l\u00F6schen? +url.template.edit=Bearbeiten +url.template.edit.title=URL Vorlage bearbeiten +url.template.id=ID +url.template.name=Name +url.template.url1=URL 1 +url.template.url2=URL 2 viewer.error.browser=Der Livestream kann in diesem Browser nicht angezeigt werden. Bitte verwenden Sie einen anderen Browser. viewer.error.stream=Der Livestream kann nicht angezeigt werden. Vermutlich wird der Livestream noch nicht ausgestrahlt. viewer.no.stream=Aktuell wird kein Livestream ausgestrahlt. diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties index 23424e7bfe2..3a884578f54 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/livestream/ui/_i18n/LocalStrings_en.properties @@ -9,13 +9,16 @@ admin.menu.title.alt=$:\admin.menu.title admin.module.enabled=Course element admin.multi.stream.enabled=Multi stream admin.player.profile=Stream +admin.settings=Settings admin.url.separator=URL separator admin.url.separator.help=Char / String to separate multiple URLs of a single event. +admin.url.templates=URL templates condition.accessibility.title=Access config.buffer.before.min=Buffer time before start (in minutes) config.buffer.after.min=Buffer time after end (in minutes) config.coach.edit=Coach is allowed to edit config.player.profile=$\:admin.player.profile +error.url.not.valid=URL not valid form.error.wrong.int=Wrong numeral format. Examples\: 2, 10, 144 link.text=Video live stream list.title=Upcoming live streams @@ -40,6 +43,15 @@ table.header.end=$org.olat.commons.calendar\:cal.form.end table.header.location=$org.olat.commons.calendar\:cal.form.location table.header.subject=$org.olat.commons.calendar\:cal.form.subject table.header.viewers=Viewers +url.template.add=Add URL template +url.template.delete=Delete +url.template.delete.confirm=Do your really want to delete the URL template "{0}"? +url.template.edit=Edit +url.template.edit.title=Edit URL template +url.template.id=ID +url.template.name=Name +url.template.url1=URL 1 +url.template.url2=URL 2 viewer.error.browser=The livestream cannot be displayed in this browser. Please use a different browser. viewer.error.stream=It is not possible to show the live stream. Probably is the live stream not broadcasted yet. viewer.no.stream=Currently no live stream is broadcasted. diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java index fd94af7dd80..5cf53e68740 100644 --- a/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java @@ -42,7 +42,7 @@ import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.LoggingObject; import org.olat.core.util.prefs.Preferences; import org.olat.core.util.prefs.db.DbStorage; -import org.olat.course.nodes.livestream.manager.LiveStreamLaunchDAO; +import org.olat.course.nodes.livestream.manager.LaunchDAO; import org.olat.modules.quality.QualityDataCollection; import org.olat.modules.quality.QualityService; import org.olat.properties.Property; @@ -82,7 +82,7 @@ public class OLATUpgrade_14_2_0 extends OLATUpgrade { @Autowired private RepositoryEntryDAO repositoryEntryDao; @Autowired - private LiveStreamLaunchDAO liveStreamLaunchDao; + private LaunchDAO liveStreamLaunchDao; @Autowired private BaseSecurity securityManager; diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 48a909947f8..999de0ab661 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -112,6 +112,7 @@ <class>org.olat.course.nodes.gta.model.TaskRevisionImpl</class> <class>org.olat.course.nodes.gta.model.TaskRevisionDateImpl</class> <class>org.olat.course.nodes.livestream.model.LaunchImpl</class> + <class>org.olat.course.nodes.livestream.model.UrlTemplateImpl</class> <class>org.olat.course.certificate.model.CertificateImpl</class> <class>org.olat.course.certificate.model.CertificateStandalone</class> <class>org.olat.course.certificate.model.CertificateLightImpl</class> diff --git a/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql b/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql index 9587559c727..b2eba18f86a 100644 --- a/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql +++ b/src/main/resources/database/mysql/alter_15_3_x_to_15_3_8.sql @@ -3,3 +3,13 @@ alter table o_bbb_meeting add column b_password varchar(64) default null; alter table o_bbb_meeting add column b_directory varchar(64) default null; alter table o_bbb_meeting add constraint bbb_dir_idx unique (b_directory); +-- Livestream +create table o_livestream_url_template ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + l_name varchar2(64) not null, + l_url1 varchar2(2048), + l_url2 varchar2(2048), + primary key (id) +); diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 3761aa106cf..001510b7d35 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -3044,6 +3044,16 @@ create table o_livestream_launch ( fk_identity bigint not null, primary key (id) ); +-- Livestream +create table o_livestream_url_template ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + l_name varchar2(64) not null, + l_url1 varchar2(2048), + l_url2 varchar2(2048), + primary key (id) +); -- grading create table o_grad_to_identity ( diff --git a/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql b/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql index 65ea60c3d10..8bc9812d3be 100644 --- a/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql +++ b/src/main/resources/database/oracle/alter_15_3_x_to_15_3_8.sql @@ -3,3 +3,13 @@ alter table o_bbb_meeting add b_password varchar2(64) default null; alter table o_bbb_meeting add b_directory varchar2(64) default null; alter table o_bbb_meeting add constraint bbb_dir_idx unique (b_directory); +-- Livestream +create table o_livestream_url_template ( + id bigserial, + creationdate timestamp not null, + lastmodified timestamp not null, + l_name varchar2(64) not null, + l_url1 varchar2(2048), + l_url2 varchar2(2048), + primary key (id) +); diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index 8b52bea73b3..fd1c199bcbf 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -3124,6 +3124,15 @@ create table o_livestream_launch ( fk_identity number(20) not null, primary key (id) ); +create table o_livestream_url_template ( + id bigserial, + creationdate timestamp not null, + lastmodified timestamp not null, + l_name varchar2(64) not null, + l_url1 varchar2(2048), + l_url2 varchar2(2048), + primary key (id) +); -- grading create table o_grad_to_identity ( diff --git a/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql b/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql index 9587559c727..282bb14f8a5 100644 --- a/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql +++ b/src/main/resources/database/postgresql/alter_15_3_x_to_15_3_8.sql @@ -3,3 +3,13 @@ alter table o_bbb_meeting add column b_password varchar(64) default null; alter table o_bbb_meeting add column b_directory varchar(64) default null; alter table o_bbb_meeting add constraint bbb_dir_idx unique (b_directory); +-- Livestream +create table o_livestream_url_template ( + id bigserial, + creationdate timestamp not null, + lastmodified timestamp not null, + l_name varchar(64) not null, + l_url1 varchar(2048), + l_url2 varchar(2048), + primary key (id) +); diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 5895c609db9..6fb5eb0d845 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -3067,6 +3067,16 @@ create table o_livestream_launch ( primary key (id) ); +create table o_livestream_url_template ( + id bigserial, + creationdate timestamp not null, + lastmodified timestamp not null, + l_name varchar(64) not null, + l_url1 varchar(2048), + l_url2 varchar(2048), + primary key (id) +); + -- grading create table o_grad_to_identity ( id bigserial, diff --git a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java b/src/test/java/org/olat/course/nodes/livestream/manager/LaunchDAOTest.java similarity index 97% rename from src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java rename to src/test/java/org/olat/course/nodes/livestream/manager/LaunchDAOTest.java index 24aa977885a..e71eabb7357 100644 --- a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java +++ b/src/test/java/org/olat/course/nodes/livestream/manager/LaunchDAOTest.java @@ -42,10 +42,10 @@ import org.springframework.beans.factory.annotation.Autowired; * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com * */ -public class LiveStreamLaunchDAOTest extends OlatTestCase { +public class LaunchDAOTest extends OlatTestCase { @Autowired - private LiveStreamLaunchDAO sut; + private LaunchDAO sut; @Autowired private DB dbInstance; diff --git a/src/test/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAOTest.java b/src/test/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAOTest.java new file mode 100644 index 00000000000..b806d8a3742 --- /dev/null +++ b/src/test/java/org/olat/course/nodes/livestream/manager/UrlTemplateDAOTest.java @@ -0,0 +1,119 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.livestream.manager; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.olat.test.JunitTestHelper.random; + +import java.util.List; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; +import org.olat.core.commons.persistence.DB; +import org.olat.course.nodes.livestream.model.UrlTemplate; +import org.olat.course.nodes.livestream.model.UrlTemplateImpl; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 4 Jan 2021<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class UrlTemplateDAOTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + + @Autowired + private UrlTemplateDAO sut; + + @Test + public void shouldCreateUrlTemplate() { + String name = random(); + + UrlTemplate urlTemplate = sut.create(name); + dbInstance.commitAndCloseSession(); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(urlTemplate.getKey()).isNotNull(); + softly.assertThat(((UrlTemplateImpl) urlTemplate).getCreationDate()).isNotNull(); + softly.assertThat(((UrlTemplateImpl) urlTemplate).getLastModified()).isNotNull(); + softly.assertThat(urlTemplate.getName()).isEqualTo(name); + softly.assertAll(); + } + + @Test + public void shouldUpdateUrlTemplate() { + UrlTemplate urlTemplate = sut.create(random()); + dbInstance.commitAndCloseSession(); + + String name = random(); + urlTemplate.setName(name); + String url1 = random(); + urlTemplate.setUrl1(url1); + String url2 = random(); + urlTemplate.setUrl2(url2); + urlTemplate = sut.update(urlTemplate); + + UrlTemplate reloaded = sut.loadByKey(urlTemplate.getKey()); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(reloaded.getName()).isEqualTo(name); + softly.assertThat(reloaded.getUrl1()).isEqualTo(url1); + softly.assertThat(reloaded.getUrl2()).isEqualTo(url2); + softly.assertAll(); + } + + @Test + public void shouldLoadAll() { + sut.create(random()); + dbInstance.commitAndCloseSession(); + + List<UrlTemplate> all = sut.loadAll(); + + assertThat(all).hasSizeGreaterThan(0); + } + + @Test + public void shouldLoadTemplateUrlByKey() { + UrlTemplate urlTemplate = sut.create(random()); + dbInstance.commitAndCloseSession(); + + UrlTemplate reloaded = sut.loadByKey(urlTemplate.getKey()); + + assertThat(reloaded).isEqualTo(urlTemplate); + } + + @Test + public void shouldDelete() { + UrlTemplate urlTemplate = sut.create(random()); + dbInstance.commitAndCloseSession(); + + sut.delete(urlTemplate); + + UrlTemplate reloaded = sut.loadByKey(urlTemplate.getKey()); + assertThat(reloaded).isNull(); + } + + + +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index c060df03a33..4ad19ccf954 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -185,7 +185,8 @@ import org.junit.runners.Suite; org.olat.course.nodes.gta.manager.GTATaskRevisionDAOTest.class, org.olat.course.nodes.gta.manager.GTAIdentityMarkDAOTest.class, org.olat.course.nodes.gta.rule.GTAReminderRuleTest.class, - org.olat.course.nodes.livestream.manager.LiveStreamLaunchDAOTest.class, + org.olat.course.nodes.livestream.manager.LaunchDAOTest.class, + org.olat.course.nodes.livestream.manager.UrlTemplateDAOTest.class, org.olat.course.nodes.members.manager.MembersManagerTest.class, org.olat.course.nodes.pf.manager.PFManagerTest.class, org.olat.course.assessment.AssessmentManagerTest.class, -- GitLab