diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties index c67e1bcfb75..93428dbe434 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties @@ -80,6 +80,7 @@ RankGainsOrLossesBetweenFirstMarkAndFinish=Rank Gains or Losses (1st Mark -> Fin IsTracked=Is Tracked NumberOfCompetitorFixes=Number of Competitor Fixes NumberOfMarkFixes=Number of Mark Fixes +NumberOfWindFixes=Number of Wind Fixes ManeuverType=Maneuver Type Tack=Tack WindSpeedVsManeuverLoss=Wind Speed in Knots (X) vs. Maneuver Loss in Meters (Y) diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties index 82c4ad2377d..d61aaa24b2d 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties @@ -79,6 +79,7 @@ RankGainsOrLossesBetweenFirstMarkAndFinish=Positions-Verluste oder -Gewinne (1te IsTracked=Ist getracked NumberOfCompetitorFixes=Anzahl der Teilnehmer-Fixes NumberOfMarkFixes=Anzahl der Tonnen-Fixes +NumberOfWindFixes=Anzahl der Windmessungen ManeuverType=Art des Manövers Tack=Bug SpeedBefore=Geschwindigkeit bevor dem Manöver diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java index b5a77441a5a..29f579a7f4f 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java @@ -56,6 +56,9 @@ public interface HasTrackedRaceContext { @Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=2) public int getNumberOfMarkFixes(); + @Statistic(messageKey="NumberOfWindFixes", resultDecimals=0, ordinal=1) + public int getNumberOfWindFixes(); + // Convenience methods for race dependent calculation to avoid code duplication public Double getRelativeScoreForCompetitor(Competitor competitor); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java index 445e93d1d39..b1f3c2c03a1 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java @@ -1,21 +1,23 @@ package com.sap.sailing.datamining.impl.data; import java.util.Calendar; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import com.sap.sailing.datamining.data.HasLeaderboardContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.common.NauticalSide; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.tracking.LineDetails; +import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; @@ -136,26 +138,10 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext { return duration; } - @Override - public int getNumberOfCompetitorFixes() { + private int getNumberOfRawFixes(Iterable tracksFor, BiFunction> trackProvider) { int number = 0; - for (Competitor competitor : getRace().getCompetitors()) { - GPSFixTrack track = getTrackedRace().getTrack(competitor); - track.lockForRead(); - try { - number += Util.size(track.getRawFixes()); - } finally { - track.unlockAfterRead(); - } - } - return number; - } - - @Override - public int getNumberOfMarkFixes() { - int number = 0; - for (Mark mark : getTrackedRace().getMarks()) { - GPSFixTrack track = getTrackedRace().getTrack(mark); + for (T trackedObject : tracksFor) { + Track track = trackProvider.apply(trackedObject, getTrackedRace()); track.lockForRead(); try { number += Util.size(track.getRawFixes()); @@ -166,6 +152,25 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext { return number; } + @Override + public int getNumberOfCompetitorFixes() { + return getNumberOfRawFixes(getRace().getCompetitors(), (competitor, trackedRace)->trackedRace.getTrack(competitor)); + } + + @Override + public int getNumberOfMarkFixes() { + return getNumberOfRawFixes(getTrackedRace().getMarks(), (mark, trackedRace)->trackedRace.getTrack(mark)); + } + + @Override + public int getNumberOfWindFixes() { + final Iterable windSources = getTrackedRace().getWindSources(); + final Iterable windSourcesToExclude = getTrackedRace().getWindSourcesToExclude(); + final Iterable windSourcesToUse = StreamSupport.stream(windSources.spliterator(), /* parallel */ false).filter(ws->!Util.contains(windSourcesToExclude, ws)). + filter(ws->ws.getType()!=WindSourceType.TRACK_BASED_ESTIMATION).collect(Collectors.toList()); + return getNumberOfRawFixes(windSourcesToUse, (windSource, trackedRace)->trackedRace.getOrCreateWindTrack(windSource)); + } + // Convenience methods for race dependent calculation to avoid code duplication public Double getRelativeScoreForCompetitor(Competitor competitor) { final TimePoint now = MillisecondsTimePoint.now(); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 758369f0b5b..1186feaffbc 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -420,7 +420,7 @@ public class DomainFactoryImpl implements DomainFactory { @Override public void addUpdateHandlers(String updateURL, String username, String password, Serializable eventId, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) throws URISyntaxException { - final URI updateURI = new URI(updateURL); + final URI updateURI = updateURL == null ? null : new URI(updateURL); CourseDesignUpdateHandler courseDesignHandler = new CourseDesignUpdateHandler( updateURI, username, password, eventId, raceDefinition.getId()); StartTimeUpdateHandler startTimeHandler = new StartTimeUpdateHandler( diff --git a/java/com.sap.sailing.feature.runtime/feature.xml b/java/com.sap.sailing.feature.runtime/feature.xml index 97df714b092..e553243efed 100644 --- a/java/com.sap.sailing.feature.runtime/feature.xml +++ b/java/com.sap.sailing.feature.runtime/feature.xml @@ -158,6 +158,6 @@ id="org.mp4parser.isoparser" download-size="0" install-size="0" - version="1.9.31"/> + version="1.9.37"/> diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index ad5b45c5274..a5ed2dbda32 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -57,7 +57,7 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.competitorimport, com.sap.sailing.expeditionconnector, com.sap.sailing.expeditionconnector.common, - org.mp4parser.isoparser;bundle-version="1.9.31", + org.mp4parser.isoparser;bundle-version="1.9.37", com.sap.sse.datamining.ui Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties index 0c9211bf492..cbd49d66454 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties @@ -1194,6 +1194,14 @@ raceIsInLiveTimePanelMode=La carrera es en directo backToLiveTimePanelMode=Volver a directo windChart=Gráfico de vientos windChartLoading=Cargando datos de vientos... +mediaNoVideosCaption=No hay rastreos de medios +mediaShowVideoCaption=Mostrar rasteo de medios +mediaShowVideoTooltip=Haga clic para mostrar rastreo de medios: {0} +mediaHideVideoCaption=Ocultar rastreo de medios +mediaHideVideoTooltip=Haga clic para ocultar rastreo de medios +mediaSelectVideoCaption={0} rastreos de medios +mediaSelectVideoTooltip=Haga clic para seleccionar rastreo de medios +mediaManageMediaCaption=Gestionar medios mediaManageMediaTooltip=Configurar clips de audio y vídeo, sincronizar tiempos showAll=Mostrar todo raceVisibilityColumn=Visibilidad @@ -1416,7 +1424,8 @@ numberOfRacesShort=Nº regatas courseGeometry=Geometría de la regata degreesToWind={0}° respecto al viento miniLeeaderboardLegendText=Muestra los puntos y el número de carreras por competidor -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} carreras +currentOfTotalRaces[one]={0,number}/{1,number} carrera noFinishedRaces=Todavía no ha finalizado ninguna carrera racesOverview=Resumen de carreras listFormatLabel=Formato de lista @@ -2075,6 +2084,8 @@ manage2SailEventIdBox=ID de evento Manage2Sail manage2SailEventURLBox=URL de evento Manage2Sail (json) manage2SailEventIdBoxTooltip=ID de evento o URL que contiene el ID de evento de Manage2Sail manage2SailPort=Puerto +audioFiles=Pistas de audio +selectMedia=Seleccionar medio swissTimingUpdateURL=Actualizar URL (por ejemplo, para la hora de salida y el feedback sobre la modificación del rumbo) swissTimingUpdateUsername=Nombre de usuario para enviar actualizaciones swissTimingUpdatePassword=Contraseña para enviar actualizaciones diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties index 61f845bcaa3..24440fcc8b2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties @@ -1194,6 +1194,14 @@ raceIsInLiveTimePanelMode=La course est en direct. backToLiveTimePanelMode=Retour au direct windChart=Graphique du vent windChartLoading=Chargement des données de vent... +mediaNoVideosCaption=Aucune piste média +mediaShowVideoCaption=Afficher piste média +mediaShowVideoTooltip=Cliquer pour afficher la piste média : {0} +mediaHideVideoCaption=Masquer piste média +mediaHideVideoTooltip=Cliquer pour masquer la piste média +mediaSelectVideoCaption={0} pistes médias +mediaSelectVideoTooltip=Cliquer pour sélectionner la piste média +mediaManageMediaCaption=Gestion des médias mediaManageMediaTooltip=Configurer des clips audio et vidéo, synchroniser durées showAll=Afficher tout raceVisibilityColumn=Visibilité @@ -1416,7 +1424,8 @@ numberOfRacesShort=Nombre de courses courseGeometry=Géométrie du parcours degreesToWind={0}° au vent miniLeeaderboardLegendText=Affiche les points associés au nombre de courses par concurrent -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} courses +currentOfTotalRaces[one]={0,number}/{1,number} course noFinishedRaces=Aucune course terminée pour l''instant. racesOverview=Synthèse des courses listFormatLabel=Format de liste @@ -2075,6 +2084,8 @@ manage2SailEventIdBox=ID de manifestation Manage2Sail manage2SailEventURLBox=URL de la manifestation Manage2Sail (json) manage2SailEventIdBoxTooltip=ID d''événement ou URL contenant l''ID d''événement issu de Manage2Sail manage2SailPort=Bâbord +audioFiles=Pistes audio +selectMedia=Sélectionner média swissTimingUpdateURL=Mettre à jour URL (par exemple pour l''heure de début ou pour les commentaires sur le changement de trajectoire) swissTimingUpdateUsername=Nom d''utilisateur pour l''envoi des mises à jour swissTimingUpdatePassword=Mot de passe pour l''envoi des mises à jour diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties index 3182dbc677d..ba47234d5f7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties @@ -1194,6 +1194,14 @@ raceIsInLiveTimePanelMode=レースがライブ中 backToLiveTimePanelMode=ライブに戻る windChart=風チャート windChartLoading=風データをロード中... +mediaNoVideosCaption=メディアトラックなし +mediaShowVideoCaption=メディアトラック表示 +mediaShowVideoTooltip=クリックしてメディアトラックを表示: {0} +mediaHideVideoCaption=メディアトラック非表示 +mediaHideVideoTooltip=クリックしてメディアトラックを非表示 +mediaSelectVideoCaption={0} メディアトラック +mediaSelectVideoTooltip=クリックしてメディアトラックを選択 +mediaManageMediaCaption=メディア管理 mediaManageMediaTooltip=音声および動画クリックを設定して、時間を同期 showAll=全表示 raceVisibilityColumn=可視性 @@ -1416,7 +1424,8 @@ numberOfRacesShort=# レース courseGeometry=コース配置 degreesToWind=風と {0}° miniLeeaderboardLegendText=競技者別にレース数とともに得点を表示 -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} レース +currentOfTotalRaces[one]={0,number}/{1,number} レース noFinishedRaces=フィニッシュしたレースがまだありません。 racesOverview=レース概要 listFormatLabel=レース一覧 @@ -2075,6 +2084,8 @@ manage2SailEventIdBox=Manage2Sail イベント ID manage2SailEventURLBox=Manage2Sail イベント URL (JSON) manage2SailEventIdBoxTooltip=イベント ID または Manage2Sail からのイベント ID が入っている URL manage2SailPort=ポート +audioFiles=音声トラック +selectMedia=メディア選択 swissTimingUpdateURL=URL の更新 (スタート時刻やコース変更のフィードバックなど) swissTimingUpdateUsername=更新送信のユーザ名 swissTimingUpdatePassword=更新送信のパスワード diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties index 14a935a589d..02a6fa05f81 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties @@ -1194,6 +1194,14 @@ raceIsInLiveTimePanelMode=Corrida ao vivo backToLiveTimePanelMode=Voltar ao tempo real windChart=Carta de ventos windChartLoading=Carregando dados do vento... +mediaNoVideosCaption=Nenhuma faixa de mídia +mediaShowVideoCaption=Exibir faixa de mídia +mediaShowVideoTooltip=Clicar para exibir faixa de mídia: {0} +mediaHideVideoCaption=Ocultar faixa de mídia +mediaHideVideoTooltip=Clicar para ocultar faixa de mídia +mediaSelectVideoCaption={0} faixas de mídia +mediaSelectVideoTooltip=Clicar para selecionar faixa de mídia +mediaManageMediaCaption=Administrar mídia mediaManageMediaTooltip=Configurar clipes de áudio e vídeo, sincronizar horas showAll=Visualizar tudo raceVisibilityColumn=Visibilidade @@ -1416,7 +1424,8 @@ numberOfRacesShort=Nº de corridas courseGeometry=Geometria do percurso degreesToWind={0}° para vento miniLeeaderboardLegendText=Exibe os pontos juntamente com o número de corridas por competidor -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} corridas +currentOfTotalRaces[one]={0,number}/{1,number} corrida noFinishedRaces=Ainda não existem corridas concluídas. racesOverview=Síntese de corridas listFormatLabel=Formato de lista @@ -2075,6 +2084,8 @@ manage2SailEventIdBox=ID de evento Manage2Sail manage2SailEventURLBox=URL de evento Manage2Sail (json) manage2SailEventIdBoxTooltip=ID de evento ou um URL que contenha o ID de evento do Manage2Sail manage2SailPort=Porta +audioFiles=Faixas de áudio +selectMedia=Selecionar mídia swissTimingUpdateURL=Atualizar URL (por exemplo, para hora de partida e feedback de mudança de percurso) swissTimingUpdateUsername=Nome de usuário para enviar atualizações swissTimingUpdatePassword=Senha para enviar atualizações diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 36024486f96..3d8197dc0a1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1194,6 +1194,14 @@ raceIsInLiveTimePanelMode=Гонка активна backToLiveTimePanelMode=Вернуться в оперативный режим windChart=Диаграмма ветра windChartLoading=Загрузка данных о ветре... +mediaNoVideosCaption=Нет объектов мультимедиа +mediaShowVideoCaption=Показать объект мультимедиа +mediaShowVideoTooltip=Щелкните, чтобы показать объект мультимедиа: {0} +mediaHideVideoCaption=Скрыть объект мультимедиа +mediaHideVideoTooltip=Щелкните, чтобы скрыть объект мультимедиа +mediaSelectVideoCaption=Объекты мультимедиа: {0} +mediaSelectVideoTooltip=Щелкните, чтобы выбрать объект мультимедиа +mediaManageMediaCaption=Управление мультимедиа mediaManageMediaTooltip=Настройка аудио- и видеоклипов, синхронизация времени showAll=Показать все raceVisibilityColumn=Видимость @@ -1416,7 +1424,8 @@ numberOfRacesShort=Гонки courseGeometry=Геометрия курса degreesToWind={0}° к ветру miniLeeaderboardLegendText=Показывает баллы и число гонок каждого участника -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} гонок +currentOfTotalRaces[one]={0,number}/{1,number} гонка noFinishedRaces=Завершенных гонок еще нет racesOverview=Обзор гонок listFormatLabel=Формат списка @@ -2075,6 +2084,8 @@ manage2SailEventIdBox=Ид. события Manage2Sail manage2SailEventURLBox=URL события Manage2Sail (json) manage2SailEventIdBoxTooltip=Ид. события или URL, содержащий ид. события из Manage2Sail manage2SailPort=Порт +audioFiles=Аудиообъект +selectMedia=Выбрать мультимедиа swissTimingUpdateURL=Обновить URL (например, для времени начала и отзыва об изменении курса) swissTimingUpdateUsername=Имя пользователя для отправки обновлений swissTimingUpdatePassword=Пароль для отправки обновлений diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 1626d270d6e..0a36b356408 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -1194,6 +1194,14 @@ raceIsInLiveTimePanelMode=正在直播比赛轮次 backToLiveTimePanelMode=返回实况 windChart=风力记录 windChartLoading=正在加载风力数据... +mediaNoVideosCaption=无媒体轨道 +mediaShowVideoCaption=显示媒体轨道 +mediaShowVideoTooltip=点击显示媒体轨道:{0} +mediaHideVideoCaption=隐藏媒体轨道 +mediaHideVideoTooltip=点击隐藏媒体轨道 +mediaSelectVideoCaption={0}个媒体轨道 +mediaSelectVideoTooltip=点击选择媒体轨道 +mediaManageMediaCaption=管理媒体 mediaManageMediaTooltip=配置音频和视频短片,同步时间 showAll=显示全部 raceVisibilityColumn=可见性 @@ -1416,7 +1424,8 @@ numberOfRacesShort=比赛轮次数 courseGeometry=场地几何结构 degreesToWind={0}°(相对于风) miniLeeaderboardLegendText=显示分数以及各参赛队的比赛轮次数 -currentOfTotal={0,number}/{1,number} +currentOfTotalRaces={0,number}/{1,number} 个比赛轮次 +currentOfTotalRaces[one]={0,number}/{1,number} 个比赛轮次 noFinishedRaces=没有结束的比赛轮次 racesOverview=比赛轮次总览 listFormatLabel=清单格式 @@ -2075,6 +2084,8 @@ manage2SailEventIdBox=Manage2Sail 活动编号 manage2SailEventURLBox=Manage2Sail 活动 URL (json) manage2SailEventIdBoxTooltip=包含 Manage2Sail 中活动编号的活动编号或 URL manage2SailPort=左舷 +audioFiles=音频轨道 +selectMedia=选择媒体 swissTimingUpdateURL=更新 URL(例如开始时间和航向改变反馈) swissTimingUpdateUsername=用于发送更新的用户名 swissTimingUpdatePassword=用于发送更新的密码 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java index af3862d4506..931003beea2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java @@ -56,6 +56,7 @@ public class BoatOverlay extends CanvasOverlayV3 { private Integer lastHeight; private Size lastScale; private Color lastColor; + private DisplayMode lastDisplayMode; public static enum DisplayMode { DEFAULT, SELECTED, NOT_SELECTED }; private DisplayMode displayMode; @@ -93,7 +94,8 @@ public class BoatOverlay extends CanvasOverlayV3 { if (lastWidth == null || canvasWidth != lastWidth || lastHeight == null || canvasHeight != lastHeight) { setCanvasSize(canvasWidth, canvasHeight); } - if (needToDraw(boatFix.legType, boatFix.tack, isSelected(), canvasWidth, canvasHeight, boatSizeScaleFactor, color)) { + if (needToDraw(boatFix.legType, boatFix.tack, isSelected(), canvasWidth, canvasHeight, boatSizeScaleFactor, + color, displayMode)) { boatVectorGraphics.drawBoatToCanvas(getCanvas().getContext2d(), boatFix.legType, boatFix.tack, getDisplayMode(), canvasWidth, canvasHeight, boatSizeScaleFactor, color); lastLegType = boatFix.legType; @@ -103,6 +105,7 @@ public class BoatOverlay extends CanvasOverlayV3 { lastHeight = canvasHeight; lastScale = boatSizeScaleFactor; lastColor = color; + lastDisplayMode = displayMode; } LatLng latLngPosition = coordinateSystem.toLatLng(boatFix.position); Point boatPositionInPx = mapProjection.fromLatLngToDivPixel(latLngPosition); @@ -123,11 +126,12 @@ public class BoatOverlay extends CanvasOverlayV3 { * changed, the result is true. */ private boolean needToDraw(LegType legType, Tack tack, boolean isSelected, double width, double height, - Size scaleFactor, Color color) { + Size scaleFactor, Color color, DisplayMode displayMode) { return lastLegType == null || lastLegType != legType || lastTack == null || lastTack != tack || lastSelected == null || lastSelected != isSelected || lastWidth == null || lastWidth != width || lastHeight == null || lastHeight != height || lastScale == null || !lastScale.equals(scaleFactor) - || lastColor == null || !lastColor.equals(color); + || lastColor == null || !lastColor.equals(color) || lastDisplayMode == null + || !lastDisplayMode.equals(displayMode); } /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index 4927bcdd6c4..be65fdd6105 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -70,7 +70,6 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.Bounds; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -175,6 +174,7 @@ public class RaceMap extends AbstractCompositeComponent impleme private AbsolutePanel rootPanel = new AbsolutePanel(); private MapWidget map; + private Collection mapInitializedListener; /** * Always valid, non-null. Must be used to map all coordinates, headings, bearings, and directions @@ -481,6 +481,14 @@ public class RaceMap extends AbstractCompositeComponent impleme public boolean contains(T t) { return (map.containsKey(t)); } + + public void clear() { + map.clear(); + } + + public void removeAll(T t) { + map.remove(t); + } } private class AdvantageLineUpdater implements QuickRanksListener { @@ -546,6 +554,7 @@ public class RaceMap extends AbstractCompositeComponent impleme updateCoordinateSystemFromSettings(); lastTimeChangeBeforeInitialization = null; isMapInitialized = false; + mapInitializedListener = new ArrayList<>(); this.hasPolar = false; headerPanel = new FlowPanel(); headerPanel.setStyleName("RaceMap-HeaderPanel"); @@ -764,6 +773,21 @@ public class RaceMap extends AbstractCompositeComponent impleme if (streamletOverlay != null && settings.isShowWindStreamletOverlay()) { streamletOverlay.setCanvasSettings(); } + if (!currentlyDragging) { + refreshMapWithoutAnimation(); + } + if (!mapFirstZoomDone) { + zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this)); + redraw(); + } + if (!isMapInitialized) { + RaceMap.this.isMapInitialized = true; + mapInitializedListener.forEach(c -> c.run()); + mapInitializedListener.clear(); + // ensure at least one redraw after starting the map, but not before other things like modes + // initialize, as they might change the timer or other settings + redraw(); + } } }); map.addBoundsChangeHandler(new BoundsChangeMapHandler() { @@ -777,7 +801,7 @@ public class RaceMap extends AbstractCompositeComponent impleme currentMapBounds = map.getBounds(); currentZoomLevel = newZoomLevel; headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX); - refreshMapWithoutAnimation(); + refreshMapWithoutAnimationButLeaveTransitionsAlive(); } }); @@ -812,7 +836,6 @@ public class RaceMap extends AbstractCompositeComponent impleme createSettingsButton(map); } // Data has been initialized - RaceMap.this.isMapInitialized = true; RaceMap.this.redraw(); trueNorthIndicatorPanel.redraw(); showAdditionalControls(map); @@ -910,6 +933,7 @@ public class RaceMap extends AbstractCompositeComponent impleme } public void redraw() { + remoteCallsToSkipInExecution.removeAll(timer.getTime()); timeChanged(timer.getTime(), null); } @@ -964,6 +988,10 @@ public class RaceMap extends AbstractCompositeComponent impleme return timer.getPlayMode() == PlayModes.Live; } + private void refreshMapWithoutAnimationButLeaveTransitionsAlive() { + remoteCallsToSkipInExecution.addAll(remoteCallsInExecution); + } + private void refreshMapWithoutAnimation() { removeTransitions(); remoteCallsToSkipInExecution.addAll(remoteCallsInExecution); @@ -1041,7 +1069,6 @@ public class RaceMap extends AbstractCompositeComponent impleme }); } else { - GWT.log("added identical call to skip"); remoteCallsToSkipInExecution.add(newTime); } } @@ -1049,8 +1076,8 @@ public class RaceMap extends AbstractCompositeComponent impleme @Override public void timeChanged(final Date newTime, final Date oldTime) { boolean isRedraw = oldTime == null; - if (newTime != null && isMapInitialized) { - if (raceIdentifier != null) { + if (isMapInitialized) { + if (newTime != null) { if (raceIdentifier != null) { final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime); refreshMap(newTime, transitionTimeInMillis, isRedraw); @@ -1203,28 +1230,32 @@ public class RaceMap extends AbstractCompositeComponent impleme showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions); showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); - + // Rezoom the map LatLngBounds zoomToBounds = null; - if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual + if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { + // Auto zoom if setting is not manual + zoomToBounds = settings.getZoomSettings().getNewBounds(RaceMap.this); if (zoomToBounds == null && !mapFirstZoomDone) { - zoomToBounds = getDefaultZoomBounds(); // the user-specified zoom couldn't find what it was looking for; try defaults once + // the user-specified zoom couldn't find what it was looking for; try defaults once + zoomToBounds = getDefaultZoomBounds(); } - } else if (!mapFirstZoomDone) { // Zoom once to the marks if marks exist + } + if (!mapFirstZoomDone) { + // Zoom once to the marks if marks exist zoomToBounds = new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this); if (zoomToBounds == null) { - zoomToBounds = getDefaultZoomBounds(); // use default zoom, e.g., + // use default zoom, e.g., + zoomToBounds = getDefaultZoomBounds(); } /* - * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that - * the map was just zoomed to the bounds of the marks, it was not a zoom or pan - * triggered by the user. As a consequence the - * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. + * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that the map was + * just zoomed to the bounds of the marks, it was not a zoom or pan triggered by the user. + * As a consequence the mapZoomedOrPannedSinceLastRaceSelection option has to reset again. */ } zoomMapToNewBounds(zoomToBounds); - mapFirstZoomDone = true; updateEstimatedDuration(raceMapDataDTO.estimatedDuration); } } else { @@ -1277,7 +1308,7 @@ public class RaceMap extends AbstractCompositeComponent impleme private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis, final Map hasTailOverlapForCompetitor, final Iterable competitorsToShow, Map> boatData, boolean updateTailsOnly) { - if (zoomingAnimationsInProgress == 0 && !currentlyDragging) { + if (zoomingAnimationsInProgress == 0) { fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); showBoatsOnMap(newTime, transitionTimeInMillis, /* re-calculate; it could have changed since the asynchronous request was made: */ @@ -2076,18 +2107,13 @@ public class RaceMap extends AbstractCompositeComponent impleme int zoomLat = (int) Math.floor(Math.log(map.getDiv().getClientHeight() * 2 * Math.PI / deltaLat / GLOBE_PXSIZE) / LOG2); return Math.min(Math.min(zoomLat, zoomLng), MAX_ZOOM); } - + private void zoomMapToNewBounds(LatLngBounds newBounds) { if (newBounds != null) { - LatLngBounds currentMapBounds; - if (map.getBounds() == null - || !BoundsUtil.contains((currentMapBounds = map.getBounds()), newBounds) - || graticuleAreaRatio(currentMapBounds, newBounds) > 10) { - // only change bounds if the new bounds don't fit into the current map zoom + int newZoomLevel = getZoomLevel(newBounds); + if (mapNeedsToPanOrZoom(newBounds, newZoomLevel)) { Iterable oldZoomTypesToConsiderSettings = settings.getZoomSettings().getTypesToConsiderOnZoom(); setAutoZoomInProgress(true); - autoZoomLatLngBounds = newBounds; - int newZoomLevel = getZoomLevel(autoZoomLatLngBounds); if (newZoomLevel != map.getZoom()) { // distinguish between zoom-in and zoom-out, because the sequence of panTo() and setZoom() // appears different on the screen due to map-animations @@ -2097,37 +2123,39 @@ public class RaceMap extends AbstractCompositeComponent impleme autoZoomIn = newZoomLevel > map.getZoom(); autoZoomOut = !autoZoomIn; autoZoomLevel = newZoomLevel; - removeTransitions(); if (autoZoomIn) { - map.panTo(autoZoomLatLngBounds.getCenter()); + map.panTo(newBounds.getCenter()); } else { map.setZoom(autoZoomLevel); } } else { - map.panTo(autoZoomLatLngBounds.getCenter()); + map.panTo(newBounds.getCenter()); } + autoZoomLatLngBounds = newBounds; RaceMapZoomSettings restoredZoomSettings = new RaceMapZoomSettings(oldZoomTypesToConsiderSettings, settings.getZoomSettings().isZoomToSelectedCompetitors()); settings = new RaceMapSettings(settings, restoredZoomSettings); setAutoZoomInProgress(false); + mapFirstZoomDone = true; } } } + + private boolean mapNeedsToPanOrZoom(LatLngBounds newBounds, int newZoomLevel) { + // we never updated, update now + if (currentMapBounds == null) { + return true; + } + // we do not fit the required bounds, update now + if (!BoundsUtil.contains(currentMapBounds, newBounds)) { + return true; + } + // we do fit the required bounds, however we might be to far zoomed out, check if we can zoom to a better level + if (newZoomLevel != map.getZoom()) { + return true; + } + return false; + } - private double graticuleAreaRatio(LatLngBounds containing, LatLngBounds contained) { - assert BoundsUtil.contains(containing, contained); - double containingAreaRatio = getGraticuleArea(containing) / getGraticuleArea(contained); - return containingAreaRatio; - } - - /** - * A much simplified "area" calculation for a {@link Bounds} object, multiplying the differences in latitude and longitude degrees. - * The result therefore is in the order of magnitude of 60*60 square nautical miles. - */ - private double getGraticuleArea(LatLngBounds bounds) { - return ((BoundsUtil.isCrossesDateLine(bounds) ? bounds.getNorthEast().getLongitude()+360 : bounds.getNorthEast().getLongitude())-bounds.getSouthWest().getLongitude()) * - (bounds.getNorthEast().getLatitude() - bounds.getSouthWest().getLatitude()); - } - private void setAutoZoomInProgress(boolean autoZoomInProgress) { this.autoZoomInProgress = autoZoomInProgress; } @@ -2563,9 +2591,6 @@ public class RaceMap extends AbstractCompositeComponent impleme } } showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); - } else { - // adding a single competitor; may need to re-load data, so refresh: - redraw(); } } else { // only change highlighting @@ -2574,11 +2599,6 @@ public class RaceMap extends AbstractCompositeComponent impleme boatCanvas.setDisplayMode(displayHighlighted(competitor)); boatCanvas.draw(); showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); - } else { - // seems like an internal error not to find the lowlighted marker; but maybe the - // competitor was added late to the race; - // data for newly selected competitor supposedly missing; refresh - redraw(); } } // Now update tails for all competitors because selection change may also affect all unselected competitors @@ -2594,6 +2614,7 @@ public class RaceMap extends AbstractCompositeComponent impleme if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) { zoomMapToNewBounds(zoomSettings.getNewBounds(this)); } + redraw(); } @Override @@ -2865,7 +2886,9 @@ public class RaceMap extends AbstractCompositeComponent impleme public void onResize() { if (map != null) { map.triggerResize(); - zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this)); + if (isMapInitialized) { + zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this)); + } } // Adjust RaceMap headers to avoid overlapping based on the RaceMap width boolean isCompactHeader = this.getOffsetWidth() <= 600; @@ -3121,4 +3144,13 @@ public class RaceMap extends AbstractCompositeComponent impleme public RaceMapLifecycle getLifecycle() { return raceMapLifecycle; } + + public void addMapInitializedListener(Runnable runnable) { + if (isMapInitialized) { + runnable.run(); + } else { + this.mapInitializedListener.add(runnable); + } + } } + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractRaceBoardMode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractRaceBoardMode.java index 50ca1584ec9..88c75df95a0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractRaceBoardMode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/AbstractRaceBoardMode.java @@ -78,8 +78,19 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI leaderboardPanel.setAutoExpandPreSelected(true); this.timer = raceBoardPanel.getTimer(); this.raceIdentifier = raceBoardPanel.getSelectedRaceIdentifier(); + raceBoardPanel.getMap().addMapInitializedListener(new Runnable() { + public void run() { + checkIfTrigger(); + }; + }); } + protected void checkIfTrigger() { + if (raceBoardPanel.getMap().isDataInitialized()) { + trigger(); + } + } + /** * Called whenever new information of any sort has become available. Subclasses can use this * to decide whether everything they need has been received in order to carry out their actions. @@ -118,7 +129,7 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) { this.raceTimesInfo = raceTimesInfo; this.raceTimesInfoForRace = raceTimesInfo.get(getRaceIdentifier()); - trigger(); + checkIfTrigger(); } protected void stopReceivingRaceTimesInfos() { @@ -131,7 +142,8 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI @Override public void updatedLeaderboard(LeaderboardDTO leaderboard) { this.leaderboard = leaderboard; - trigger(); + checkIfTrigger(); + ; } protected void stopReceivingLeaderboard() { @@ -140,9 +152,9 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI /** * Fetches the leaderboard named {@code leaderboardName} for {@code timePoint} with details for race column - * {@code raceColumnName} and stores it in {@link #leaderboardForSpecificTimePoint}, then invokes {@link #trigger()}. - * If a {@link #getLeaderboard()} has been received already, it is passed on in the call in order to reduce the - * bandwidth required, based on a differential approach. + * {@code raceColumnName} and stores it in {@link #leaderboardForSpecificTimePoint}, then invokes + * {@link #checkIfTrigger()}. If a {@link #getLeaderboard()} has been received already, it is passed on in the call + * in order to reduce the bandwidth required, based on a differential approach. */ protected void loadLeaderboardForSpecificTimePoint(String leaderboardName, String raceColumnName, Date timePoint) { @@ -156,7 +168,7 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI public void onSuccess(LeaderboardDTO result) { leaderboardForSpecificTimePoint = result; getLeaderboardPanel().updateLeaderboard(result); - trigger(); + checkIfTrigger(); } }; final ArrayList raceColumnNameAsList = new ArrayList<>(); @@ -174,7 +186,7 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI @Override public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) { this.raceColumn = raceColumn; - trigger(); + checkIfTrigger(); } protected Map getRaceTimesInfo() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardModeWithPerRaceCompetitors.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardModeWithPerRaceCompetitors.java index 6d8ce6b8bc6..9916e253194 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardModeWithPerRaceCompetitors.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardModeWithPerRaceCompetitors.java @@ -42,7 +42,7 @@ public abstract class RaceBoardModeWithPerRaceCompetitors extends AbstractRaceBo @Override public void competitorsForRaceDefined(Iterable competitorsInRace) { this.competitorsInRace = competitorsInRace; - trigger(); + checkIfTrigger(); } protected Iterable getCompetitorsInRace() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java index 16d309f6e26..5d36b5316c8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java @@ -1,16 +1,13 @@ package com.sap.sailing.gwt.ui.server; import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; -import java.lang.reflect.Field; import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.URL; @@ -23,29 +20,21 @@ import java.nio.file.Files; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.shiro.SecurityUtils; +import org.mp4parser.AbstractBoxParser; import org.mp4parser.IsoFile; -import org.mp4parser.boxes.UserBox; -import org.mp4parser.boxes.iso14496.part12.MediaDataBox; -import org.mp4parser.boxes.iso14496.part12.MovieBox; -import org.mp4parser.boxes.iso14496.part12.MovieHeaderBox; -import org.mp4parser.tools.Path; +import org.mp4parser.PropertyBoxParserImpl; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import com.google.gwt.thirdparty.json.JSONArray; import com.google.gwt.thirdparty.json.JSONException; import com.google.gwt.thirdparty.json.JSONObject; import com.google.gwt.user.server.rpc.RemoteServiceServlet; @@ -55,6 +44,8 @@ import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.security.Permission; import com.sap.sailing.domain.common.security.Permission.Mode; import com.sap.sailing.gwt.ui.client.MediaService; +import com.sap.sailing.media.mp4.MP4MediaParser; +import com.sap.sailing.media.mp4.MP4ParserFakeFile; import com.sap.sailing.server.RacingEventService; import com.sap.sse.common.Duration; import com.sap.sse.common.impl.MillisecondsDurationImpl; @@ -70,7 +61,6 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi private ServiceTracker racingEventServiceTracker; - private static final int REQUIRED_SIZE_IN_BYTES = 10000000; private static final long serialVersionUID = -8917349579281305977L; public MediaServiceImpl() { @@ -158,14 +148,14 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi URL input = new URL(url); // check size and do rangerequests if possible long fileSize = determineFileSize(input); - if (fileSize > 2 * REQUIRED_SIZE_IN_BYTES) { + if (fileSize > 2 * MP4MediaParser.REQUIRED_SIZE_IN_BYTES) { response = checkMetadataByPartialDownloads(input, fileSize); } else { response = checkMetadataByFullFileDownload(input); } } catch (Exception e) { logger.log(Level.WARNING, "Error in video analysis ", e); - response = new VideoMetadataDTO(false, null, false, null, e.getMessage()); + response = new VideoMetadataDTO(false, null, false, null, ""); } return response; } @@ -198,10 +188,10 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi private VideoMetadataDTO checkMetadataByPartialDownloads(URL input, long fileSize) throws IOException, ProtocolException { - byte[] start = new byte[REQUIRED_SIZE_IN_BYTES]; - byte[] end = new byte[REQUIRED_SIZE_IN_BYTES]; - downloadPartOfFile(input, start, "bytes=0-" + REQUIRED_SIZE_IN_BYTES); - downloadPartOfFile(input, end, "bytes=" + (fileSize - REQUIRED_SIZE_IN_BYTES) + "-"); + byte[] start = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES]; + byte[] end = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES]; + downloadPartOfFile(input, start, "bytes=0-" + MP4MediaParser.REQUIRED_SIZE_IN_BYTES); + downloadPartOfFile(input, end, "bytes=" + (fileSize - MP4MediaParser.REQUIRED_SIZE_IN_BYTES) + "-"); long skipped = fileSize - start.length - end.length; return checkMetadata(start, end, skipped); } @@ -212,9 +202,10 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi connection.setConnectTimeout(METADATA_CONNECTION_TIMEOUT); connection.setRequestMethod("GET"); connection.setRequestProperty("Range", range); - try (InputStream inStream = connection.getInputStream()) { - DataInputStream dataInStream = new DataInputStream(inStream); - dataInStream.readFully(store); + try (final InputStream inStream = connection.getInputStream()) { + try (final DataInputStream dataInStream = new DataInputStream(inStream)) { + dataInStream.readFully(store); + } } finally { connection.disconnect(); } @@ -223,146 +214,53 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi private VideoMetadataDTO checkMetadataByFullFileDownload(URL input) throws ParserConfigurationException, SAXException, IOException { final File tmp = File.createTempFile("upload", "metadataCheck"); + VideoMetadataDTO result; try { - final ReadableByteChannel rbc = Channels.newChannel(input.openStream()); - try (FileOutputStream fos = new FileOutputStream(tmp)) { - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - try (IsoFile isof = new IsoFile(tmp)) { - final boolean canDownload = true; - final Date recordStartedTimer = determineRecordingStart(isof); - final Duration duration = determineDuration(isof); - final boolean spherical = determine360(isof); - removeTempFiles(isof); - return new VideoMetadataDTO(canDownload, duration, spherical, recordStartedTimer, ""); + try (final ReadableByteChannel rbc = Channels.newChannel(input.openStream())) { + try (final FileOutputStream fos = new FileOutputStream(tmp)) { + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + try (final MP4ParserFakeFile inputFile = new MP4ParserFakeFile(tmp)) { + Files.delete(tmp.toPath()); + result = checkMetadata(inputFile); + } catch (Exception e) { + result = new VideoMetadataDTO(true, null, false, null, e.getMessage()); + } + } catch (Exception e) { + result = new VideoMetadataDTO(false, null, false, null, e.getMessage()); } } } finally { - Files.delete(tmp.toPath()); + // delete the file if not already deleted in main path + tmp.delete(); } + return result; } @Override public VideoMetadataDTO checkMetadata(byte[] start, byte[] end, Long skipped) { ensureUserCanManageMedia(); - File tmp = null; - boolean spherical = false; - Duration duration = null; - Date recordStartedTimer = null; - String message = ""; - try { - tmp = createFileFromData(start, end, skipped); - try (IsoFile isof = new IsoFile(tmp)) { - try { - recordStartedTimer = determineRecordingStart(isof); - spherical = determine360(isof); - duration = determineDuration(isof); - } finally { - removeTempFiles(isof); - } - } + VideoMetadataDTO result; + try (MP4ParserFakeFile input = new MP4ParserFakeFile(start, end, skipped)) { + result = checkMetadata(input); } catch (Exception e) { logger.log(Level.WARNING, "Error in video analysis ", e); - message = e.getMessage(); - } finally { - if (tmp != null) { - try { - Files.delete(tmp.toPath()); - } catch (IOException e) { - logger.log(Level.SEVERE, "Could not delete tmp mp4 file", e); - } - } + result = new VideoMetadataDTO(true, null, false, null, e.getMessage()); } - return new VideoMetadataDTO(true, duration, spherical, recordStartedTimer, message); + return result; } - /** - * Some boxes (we don't actually need to read) create internal tempfiles, we delete them here, as the VM could run - * quite long - */ - private void removeTempFiles(IsoFile isof) { - List boxesWithTempFiles = isof.getBoxes(MediaDataBox.class, true); - for (MediaDataBox box : boxesWithTempFiles) { - try { - Field field = box.getClass().getDeclaredField("dataFile"); - field.setAccessible(true); - File data = (File) field.get(box); - Files.delete(data.toPath()); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException - | IOException e) { - logger.log(Level.WARNING, "Could not delete mp4 temp files", e); - } + private VideoMetadataDTO checkMetadata(MP4ParserFakeFile input) + throws ParserConfigurationException, SAXException, IOException { + AbstractBoxParser boxParserImpl = new PropertyBoxParserImpl(); + boxParserImpl.skippingBoxes(new String[] { "mdat" }); + try (IsoFile isof = new IsoFile(input, boxParserImpl)) { + Date recordStartedTimer = MP4MediaParser.determineRecordingStart(isof); + boolean spherical = MP4MediaParser.determine360(isof); + Duration duration = new MillisecondsDurationImpl(MP4MediaParser.determineDurationInMillis(isof)); + return new VideoMetadataDTO(true, duration, spherical, recordStartedTimer, ""); } } - /** - * Creates a dummy mp4 file, only containing the start and the end, the middle is filled with 0 so that all file - * internal pointers from start to end are working - */ - private File createFileFromData(byte[] start, byte[] end, Long skipped) throws IOException, FileNotFoundException { - File tmp; - tmp = File.createTempFile("upload", "metadataCheck"); - try (FileOutputStream fw = new FileOutputStream(tmp)) { - fw.write(start); - while (skipped > 0) { - // ensure that no absurd amount of memory is required, and that more then 4gb files can be analysed - if (skipped > 10000000) { - byte[] dummy = new byte[10000000]; - fw.write(dummy); - skipped = skipped - 10000000; - } else { - byte[] dummy = new byte[skipped.intValue()]; - fw.write(dummy); - skipped = skipped - skipped.intValue(); - } - } - fw.write(end); - } - return tmp; - } - - private boolean determine360(IsoFile isof) throws ParserConfigurationException, SAXException, IOException { - boolean spherical = false; - UserBox uuidBox = Path.getPath(isof, "moov[0]/trak[0]/uuid"); - if (uuidBox != null) { - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - Document doc = dBuilder.parse(new ByteArrayInputStream(uuidBox.getData())); - - NodeList childs = doc.getDocumentElement().getChildNodes(); - for (int i = 0; i < childs.getLength(); i++) { - Node child = childs.item(i); - if (child.getNodeName().toLowerCase().contains(":spherical")) { - spherical = true; - } - } - } - return spherical; - } - - private Duration determineDuration(IsoFile isof) { - Duration duration = null; - MovieBox mbox = isof.getMovieBox(); - if (mbox != null) { - MovieHeaderBox mhb = mbox.getMovieHeaderBox(); - if (mhb != null) { - duration = new MillisecondsDurationImpl(mhb.getDuration()*1000 / mhb.getTimescale()); - } - } - return duration; - } - - private Date determineRecordingStart(IsoFile isof) { - Date creationTime = null; - MovieBox mbox = isof.getMovieBox(); - if (mbox != null) { - MovieHeaderBox mhb = mbox.getMovieHeaderBox(); - if (mhb != null) { - creationTime = mhb.getCreationTime(); - } - } - return creationTime; - } - @Override public MediaTrack getMediaTrackByUrl(String url) { MediaTrack result = null; @@ -384,6 +282,7 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi if (videoId.isEmpty()) { message = "Empty id"; } else { + // sanitize, as we inject it into an url with our api key! videoId = URLEncoder.encode(videoId, StandardCharsets.UTF_8.name()); try { URL apiURL = new URL( @@ -396,10 +295,13 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { String pageText = reader.lines().collect(Collectors.joining("\n")); JSONObject jsonAnswer = new JSONObject(pageText); - final JSONObject item = jsonAnswer.getJSONArray("items").getJSONObject(0); - message = item.getJSONObject("snippet").getString("title"); - String rawDuration = item.getJSONObject("contentDetails").getString("duration"); - duration = new MillisecondsDurationImpl(java.time.Duration.parse(rawDuration).toMillis()); + final JSONArray dataArray = jsonAnswer.getJSONArray("items"); + if (dataArray.length() > 0) { + final JSONObject item = dataArray.getJSONObject(0); + message = item.getJSONObject("snippet").getString("title"); + String rawDuration = item.getJSONObject("contentDetails").getString("duration"); + duration = new MillisecondsDurationImpl(java.time.Duration.parse(rawDuration).toMillis()); + } canDownload = true; } catch (JSONException e) { message = e.getMessage(); @@ -410,9 +312,6 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi logger.log(Level.WARNING, "Error in youtube metadata call", e); } } - //sanitize, as we inject it into an url with our api key! - - return new VideoMetadataDTO(canDownload, duration, false, null, message); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 7d2c8fea4fa..c69e2be45ba 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -6784,13 +6784,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet RegattaLogEvent event = null; TimePoint from = mapping.getTimeRange().hasOpenBeginning() ? null : mapping.getTimeRange().from(); TimePoint to = mapping.getTimeRange().hasOpenEnd() ? null : mapping.getTimeRange().to(); - if (dto.mappedTo instanceof MarkDTO) { - Mark mark = convertToMark(((MarkDTO) dto.mappedTo), true); - event = new RegattaLogDeviceMarkMappingEventImpl(now, now, getService().getServerAuthor(), - UUID.randomUUID(), mark, mapping.getDevice(), from, to); - } else if (dto.mappedTo instanceof CompetitorWithBoatDTO) { - Competitor competitor = getService().getCompetitorAndBoatStore() - .getExistingCompetitorByIdAsString(((CompetitorWithBoatDTO) dto.mappedTo).getIdAsString()); + if (mapping.getMappedTo() instanceof Mark) { + Mark mark = (Mark) mapping.getMappedTo(); + event = new RegattaLogDeviceMarkMappingEventImpl(now, now, getService().getServerAuthor(), UUID.randomUUID(), + mark, mapping.getDevice(), from, to); + } else if (mapping.getMappedTo() instanceof Competitor) { + Competitor competitor = (Competitor) mapping.getMappedTo(); if (mapping.getDevice().getIdentifierType().equals(ExpeditionSensorDeviceIdentifier.TYPE)) { event = new RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl(now, now, getService().getServerAuthor(), UUID.randomUUID(), competitor, mapping.getDevice(), from, to); @@ -6798,11 +6797,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet event = new RegattaLogDeviceCompetitorMappingEventImpl(now, now, getService().getServerAuthor(), UUID.randomUUID(), competitor, mapping.getDevice(), from, to); } - } else if (dto.mappedTo instanceof BoatDTO) { - final Boat boat = getService().getCompetitorAndBoatStore() - .getExistingBoatByIdAsString(((BoatDTO) dto.mappedTo).getIdAsString()); - event = new RegattaLogDeviceBoatMappingEventImpl(now, now, getService().getServerAuthor(), - UUID.randomUUID(), boat, mapping.getDevice(), from, to); + } else if (mapping.getMappedTo() instanceof Boat) { + final Boat boat = (Boat) mapping.getMappedTo(); + event = new RegattaLogDeviceBoatMappingEventImpl(now, now, getService().getServerAuthor(), UUID.randomUUID(), + boat, mapping.getDevice(), from, to); } else { throw new RuntimeException("Can only map devices to competitors, boats or marks"); } @@ -6851,14 +6849,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet TimeRange timeRange = new TimeRangeImpl(from, to); if (dto.mappedTo instanceof MarkDTO) { Mark mark = convertToMark(((MarkDTO) dto.mappedTo), true); - // expect UUIDs - return new DeviceMappingImpl(mark, device, timeRange, dto.originalRaceLogEventIds, - RegattaLogDeviceMarkMappingEventImpl.class); - } else if (dto.mappedTo instanceof CompetitorWithBoatDTO) { - Competitor competitor = getService().getCompetitorAndBoatStore() - .getExistingCompetitorByIdAsString(((CompetitorWithBoatDTO) dto.mappedTo).getIdAsString()); - return new DeviceMappingImpl(competitor, device, timeRange, dto.originalRaceLogEventIds, - RegattaLogDeviceCompetitorMappingEventImpl.class); + //expect UUIDs + return new DeviceMappingImpl(mark, device, timeRange, dto.originalRaceLogEventIds, RegattaLogDeviceMarkMappingEventImpl.class); + } else if (dto.mappedTo instanceof CompetitorDTO) { + Competitor competitor = getService().getCompetitorAndBoatStore().getExistingCompetitorByIdAsString( + ((CompetitorDTO) dto.mappedTo).getIdAsString()); + return new DeviceMappingImpl(competitor, device, timeRange, dto.originalRaceLogEventIds, RegattaLogDeviceCompetitorMappingEventImpl.class); } else if (dto.mappedTo instanceof BoatDTO) { final Boat boat = getService().getCompetitorAndBoatStore() .getExistingBoatByIdAsString(dto.mappedTo.getIdAsString()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index 39744fa3005..3f2122cad43 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -228,6 +228,7 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen if (this.swarm != null) { this.swarm.stop(); this.swarm.getColorMapper().removeListener(this); + swarm = null; } } @@ -335,12 +336,10 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implemen if (getCanvas() != null) { if (isVisible) { this.startStreamlets(); - this.visible = isVisible; if (colored) { this.streamletLegend.setVisible(true); } swarm.setColors(colored); - this.startStreamlets(); if (isAttached) { Scheduler.get().scheduleDeferred(() -> addObserverIfNecessary()); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java index c3315dc9a95..358ce4ae2d7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/streamlets/Swarm.java @@ -6,6 +6,7 @@ import java.util.Map; import com.google.gwt.canvas.client.Canvas; import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.core.client.Duration; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.LatLng; @@ -54,7 +55,6 @@ public class Swarm implements TimeListener { private boolean swarmOffScreen = false; private int swarmPause = 0; - private boolean swarmContinue = true; /** * Bounds in the map's coordinate system which may have undergone rotation and translation. See the @@ -96,47 +96,95 @@ public class Swarm implements TimeListener { } public void start(final int animationIntervalMillis) { + removeBoundsChangeHandler(); fullcanvas.setCanvasSettings(); - // if map is not yet loaded, wait for it - if (map.getBounds() != null) { - startWithMap(animationIntervalMillis); - } else { - removeBoundsChangeHandler(); - BoundsChangeMapHandler handler = new BoundsChangeMapHandler() { - @Override - public void onEvent(BoundsChangeMapEvent event) { - if (swarmContinue) { - startWithMap(animationIntervalMillis); - } + if (loopTimer == null) { + loopTimer = new com.google.gwt.user.client.Timer() { + public void run() { + updateSwarmOneTick(animationIntervalMillis); } }; - boundsChangeHandlers.put(handler, map.addBoundsChangeHandler(handler)); + } + + // the map already exists, ensure swam is started + if (map.getBounds() != null) { + startSwarmIfNecessaryAndUpdateProjection(); + loopTimer.schedule(0); + } + // add handler, so the swarm updates itself without calls from the map required + BoundsChangeMapHandler handler = new BoundsChangeMapHandler() { + @Override + public void onEvent(BoundsChangeMapEvent event) { + startSwarmIfNecessaryAndUpdateProjection(); + loopTimer.schedule(0); + } + }; + boundsChangeHandlers.put(handler, map.addBoundsChangeHandler(handler)); + } + + private void startSwarmIfNecessaryAndUpdateProjection() { + if (projection == null) { + projection = new Mercator(fullcanvas, map); + + } + // ensure projection fits the map + projection.calibrate(); + // ensure canvas fits the map + updateBounds(); + if (particles == null) { + // if this is the first start, also createParticles + createParticles(); + } else { + // clear all tails, as else there will be long smears over the map + clearNextFrame = true; } } - private void startWithMap(int animationIntervalMillis) { - projection = new Mercator(fullcanvas, map); - projection.calibrate(); - updateBounds(); - if (particles == null) { - createParticles(); - } else { - clearNextFrame = true; + private void updateSwarmOneTick(int animationIntervalMillis) { + double time0 = Duration.currentTimeMillis(); + // upon zooming the swarm is shortly paused, to ensure no strange rendering during the css zoom + // animations + if (swarmPause > 1) { + swarmPause--; + } else if (swarmPause == 1) { + // ensure bounds and projections are up to date + startSwarmIfNecessaryAndUpdateProjection(); + if (zoomChanged) { + // ensure amount of particles is updated + diffPx = new Vector(0, 0); + recycleParticles(); + zoomChanged = false; + } else { + // process the offset + diffPx = fullcanvas.getDiffPx(); + } + swarmPause = 0; } - swarmContinue = true; - startLoopIfNecessary(animationIntervalMillis); + // update the swarm if it is not paused + if ((!swarmOffScreen) && (swarmPause == 0)) { + execute(diffPx); + diffPx = new Vector(0, 0); + } + double time1 = Duration.currentTimeMillis(); + // wait at least 10ms for the next iteration; try to get one iteration done every + // animationIntervalMillis if possible + double timeDelta = time1 - time0; + // log("fps: "+(1000.0/timeDelta)); + loopTimer.schedule((int) Math.max(10, animationIntervalMillis - timeDelta)); } private void removeBoundsChangeHandler() { for (HandlerRegistration reg : boundsChangeHandlers.values()) { reg.removeHandler(); } + boundsChangeHandlers.clear(); } public void stop() { - swarmContinue = false; removeBoundsChangeHandler(); + projection.clearCanvas(); + loopTimer.cancel(); } private Particle recycleOrCreateParticle(Particle particle) { @@ -226,52 +274,12 @@ public class Swarm implements TimeListener { + visibleBoundsOfField.getNorthEast().getLatitude() / 180. * Math.PI) / 2); } - private void startLoopIfNecessary(final int animationIntervalMillis) { - if (loopTimer == null) { - // Create animation-loop based on timer timeout - loopTimer = new com.google.gwt.user.client.Timer() { - public void run() { - Date time0 = new Date(); - if (swarmPause > 1) { - swarmPause--; - } else if (swarmPause == 1) { - fullcanvas.setCanvasSettings(); - projection.calibrate(); - updateBounds(); - if (zoomChanged) { - diffPx = new Vector(0, 0); - recycleParticles(); - zoomChanged = false; - } else { - diffPx = fullcanvas.getDiffPx(); - } - swarmPause = 0; - } - if ((!swarmOffScreen) && (swarmPause == 0)) { - execute(diffPx); - diffPx = new Vector(0, 0); - } - Date time1 = new Date(); - if (swarmContinue) { - // wait at least 10ms for the next iteration; try to get one iteration done every - // animationIntervalMillis if possible - long timeDelta = time1.getTime() - time0.getTime(); - // log("fps: "+(1000.0/timeDelta)); - loopTimer.schedule((int) Math.max(10, animationIntervalMillis - timeDelta)); - } else { - projection.clearCanvas(); - } - } - }; - loopTimer.schedule(animationIntervalMillis); - } - } - private void drawSwarm() { Context2d ctxt = canvas.getContext2d(); + // clearing neds to be done with a little over zero, thanks IE ctxt.setGlobalAlpha(0.06); if (clearNextFrame) { - // ctxt.setGlobalAlpha(1); + // skip this frame, as it will contain an extreme delta due to the map movement clearNextFrame = false; } else { ctxt.setGlobalCompositeOperation("destination-out"); diff --git a/java/com.sap.sailing.server.gateway/resources/shiro.ini b/java/com.sap.sailing.server.gateway/resources/shiro.ini index e3804fb7736..5effb1f225e 100755 --- a/java/com.sap.sailing.server.gateway/resources/shiro.ini +++ b/java/com.sap.sailing.server.gateway/resources/shiro.ini @@ -85,9 +85,9 @@ bearerToken = com.sap.sailing.server.security.BearerTokenOrBasicOrFormAuthentica # Secure access to additional apis used for SailRacer /api/v1/leaderboards/*/enableRaceLogForCompetitorRegistration/** = bearerToken /api/v1/leaderboards/*/denoteForTracking/** = bearerToken +/api/v1/leaderboards/*/removeTrackedRace/** = bearerToken /api/v1/regattas/updateOrCreateSeries = bearerToken /api/v1/mark/addMarkToRegatta/** = bearerToken /api/v1/mark/addMarkFix/** = bearerToken /api/v1/mark/addCourseDefinitionToRaceLog/** = bearerToken /api/v1/wind/putWind/** = bearerToken - diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index 1d1703def64..4388f50ecbc 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -119,6 +119,8 @@ import com.sap.sailing.server.gateway.serialization.coursedata.impl.WaypointJson import com.sap.sailing.server.gateway.serialization.impl.CompetitorAndBoatJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.FlatGPSFixJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.MarkJsonSerializerWithPosition; +import com.sap.sailing.server.operationaltransformation.RemoveAndUntrackRace; +import com.sap.sailing.server.operationaltransformation.StopTrackingRace; import com.sap.sse.InvalidDateException; import com.sap.sse.common.Bearing; import com.sap.sse.common.Distance; @@ -845,6 +847,18 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource { return bearingSum == null ? null : bearingSum.divide(size); } + @POST + @Produces(MediaType.APPLICATION_JSON) + @Path("{leaderboardName}/removeTrackedRace") + public Response removeTrackedRace(@PathParam("leaderboardName") String leaderboardName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName) + throws MalformedURLException, IOException, InterruptedException { + SecurityUtils.getSubject() + .checkPermission(Permission.LEADERBOARD.getStringPermissionForObjects(Mode.UPDATE, leaderboardName)); + return stopOrRemoveTrackedRace(leaderboardName, raceColumnName, fleetName, true); + } + @POST @Produces(MediaType.APPLICATION_JSON) @Path("{leaderboardName}/stoptracking") @@ -852,6 +866,11 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource { @QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName, @QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName) throws MalformedURLException, IOException, InterruptedException { SecurityUtils.getSubject().checkPermission(Permission.LEADERBOARD.getStringPermissionForObjects(Mode.UPDATE, leaderboardName)); + return stopOrRemoveTrackedRace(leaderboardName, raceColumnName, fleetName, false); + } + + private Response stopOrRemoveTrackedRace(String leaderboardName, String raceColumnName, String fleetName, boolean remove) + throws MalformedURLException, IOException, InterruptedException { final Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); final Response result; if (leaderboard == null) { @@ -869,7 +888,11 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource { if (trackedRace != null) { final Regatta regatta = trackedRace.getTrackedRegatta().getRegatta(); final RaceDefinition race = trackedRace.getRace(); - getService().stopTracking(regatta, race); + if(remove) { + getService().apply(new RemoveAndUntrackRace(trackedRace.getRaceIdentifier())); + } else { + getService().apply(new StopTrackingRace(trackedRace.getRaceIdentifier())); + } jsonResult.put("regatta", regatta.getName()); jsonResult.put("race", race.getName()); jsonResultArray.add(jsonResult); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java index 52a30c52b42..a22681d1bbb 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java @@ -296,7 +296,7 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource { break; case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS: name = "currentSpeedOverGround-kts"; - if (currentLegEntry != null) { + if (currentLegEntry != null && currentLegEntry.currentSpeedOverGroundInKnots != null) { value = roundDouble(currentLegEntry.currentSpeedOverGroundInKnots, 2); } break; diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index 607bf4d63bc..fa08f079398 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -205,6 +205,10 @@ name to make sure you get to the event you're interested in. /api/v1/leaderboards/{name}/stoptracking Stops tracking for a race + + /api/v1/leaderboards/{name}/removeTrackedRace + Removes a tracked race + /api/v1/leaderboards/{name}/denoteForTracking Denotes a race for tracking diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardRaceRemove.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardRaceRemove.html new file mode 100644 index 00000000000..99d335082d6 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardRaceRemove.html @@ -0,0 +1,57 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 + + +

SAP Sailing Analytics Webservices API Version 1.0

+

URL: /api/v1/leaderboards/{name}/removeTrackedRace?race_column={name}&fleet={name}

+ +Description: +

Removes the tracked race(s) identified by the race_column and fleet parameters. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Webservice Type:REST
Output format:Json
Mandatory parameters:None
Optional parameters:race_column: if provided, restricts stopping to races in this race column; otherwise, races from all race columns will stop tracking + if the fleet parameter matches; see below

+ fleet: if provided, stopping is restricted to races in this fleet only; otherwise, races from all fleets are stopped

Request method:POST
Example 1:curl -X POST -H "Authorization: Bearer w9YSIfr/Zf6a127SZDddkhdAtQuuv0Jm1PSXlo1Yvd0=" "http://127.0.0.1:8888/sailingserver/api/v1/leaderboards/Smartphone%20Tracking%20Test%2022.06/removeTrackedRace?race_column=K1&fleet=Blue"
+ delivers an array with the regatta and race names of the races whose tracking got stopped:
+		[{"regatta":"Smartphone Tracking Test 22.06","race":"Smartphone Tracking Test 22.06 K1 Blue"}]
+		
Example 2:curl -X POST -H "Authorization: Bearer w9YSIfr/Zf6a127SZDddkhdAtQuuv0Jm1PSXlo1Yvd0=" "http://127.0.0.1:8888/sailingserver/api/v1/leaderboards/Smartphone%20Tracking%20Test%2022.06/removeTrackedRace?race_column=K1"
+ delivers an array with the regatta and race names of the races whose tracking got stopped:
+		[{"regatta":"Smartphone Tracking Test 22.06","race":"Smartphone Tracking Test 22.06 K1 Blue"}, {"regatta":"Smartphone Tracking Test 22.06","race":"Smartphone Tracking Test 22.06 K1 Yellow"}]
+		
+
+Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/putWind.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/putWind.html index 0eb95d18407..3aef1c0f4ab 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/putWind.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/putWind.html @@ -13,6 +13,8 @@ Description:

Gets the details of a competitor

+Note that while nautically wind is usually specified in the direction it's coming from, for
+computational purposes internally we measure the wind in the direction to which it blows.

diff --git a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_es.properties b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_es.properties index a59867f77c8..ea1cd6adde9 100644 --- a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_es.properties +++ b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_es.properties @@ -1,7 +1,7 @@ Average=Promedio PairAverage=Promedio Median=Mediana -PairCollecting=Identidad +PairCollecting=Dispersión Sum=Totalizar Maximum=Máximo Minimum=Mínimo @@ -12,3 +12,4 @@ Knots=nudos Collecting=Colección ResultSignifier={0} ({1}) SignifierInUnit={0} en {1} +Identity=Identidad diff --git a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_fr.properties b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_fr.properties index d7be390337c..e621a2affd6 100644 --- a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_fr.properties +++ b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_fr.properties @@ -1,7 +1,7 @@ Average=Moyenne PairAverage=Moyenne Median=Médiane -PairCollecting=Identité +PairCollecting=Nuage de points Sum=Somme Maximum=Maximum Minimum=Minimum @@ -12,3 +12,4 @@ Knots=nd Collecting=Collection ResultSignifier={0} ({1}) SignifierInUnit={0} dans {1} +Identity=Identité diff --git a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ja.properties b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ja.properties index b42eb6efe45..c5fa46824ae 100644 --- a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ja.properties +++ b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ja.properties @@ -1,7 +1,7 @@ Average=平均 PairAverage=平均 Median=中央値 -PairCollecting=ID +PairCollecting=散布図 Sum=合計 Maximum=最大 Minimum=最小 @@ -12,3 +12,4 @@ Knots=kn Collecting=コレクション ResultSignifier={0} ({1}) SignifierInUnit={0} / {1} +Identity=ID diff --git a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_pt.properties b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_pt.properties index a9852680cf3..31fbc7c7e29 100644 --- a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_pt.properties +++ b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_pt.properties @@ -1,7 +1,7 @@ Average=Média PairAverage=Média Median=Mediana -PairCollecting=Identidade +PairCollecting=Dispersão Sum=Total Maximum=Máximo Minimum=Mínimo @@ -12,3 +12,4 @@ Knots=nó Collecting=Coleção ResultSignifier={0} ({1}) SignifierInUnit={0} em {1} +Identity=Identidade diff --git a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ru.properties b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ru.properties index 1d408b0016e..0fb44c31deb 100644 --- a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ru.properties +++ b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_ru.properties @@ -1,7 +1,7 @@ Average=Среднее PairAverage=Среднее Median=Медиана -PairCollecting=Идентичность +PairCollecting=Точечная Sum=Сумма Maximum=Максимум Minimum=Минимум @@ -12,3 +12,4 @@ Knots=уз. Collecting=Коллекция ResultSignifier={0} ({1}) SignifierInUnit={0} в {1} +Identity=Идентичность diff --git a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_zh.properties b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_zh.properties index 546dcdaf702..62fd910d843 100644 --- a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_zh.properties +++ b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_zh.properties @@ -1,7 +1,7 @@ Average=平均值 PairAverage=平均值 Median=中值 -PairCollecting=身份 +PairCollecting=散点图 Sum=求和 Maximum=最大值 Minimum=最小值 @@ -12,3 +12,4 @@ Knots=kn Collecting=集合 ResultSignifier={0} ({1}) SignifierInUnit={0}(单位:{1}) +Identity=身份 diff --git a/java/org.mp4parser.isoparser/.classpath b/java/org.mp4parser.isoparser/.classpath index 58eb394e6a4..e061dd0aebe 100644 --- a/java/org.mp4parser.isoparser/.classpath +++ b/java/org.mp4parser.isoparser/.classpath @@ -1,7 +1,7 @@ - + diff --git a/java/org.mp4parser.isoparser/META-INF/MANIFEST.MF b/java/org.mp4parser.isoparser/META-INF/MANIFEST.MF index 4909aeac7c7..11deee0d099 100644 --- a/java/org.mp4parser.isoparser/META-INF/MANIFEST.MF +++ b/java/org.mp4parser.isoparser/META-INF/MANIFEST.MF @@ -2,11 +2,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Selenium Bundle-SymbolicName: org.mp4parser.isoparser -Bundle-Version: 1.9.31 -Bundle-ClassPath: lib/isoparser-1.9.31.jar +Bundle-Version: 1.9.37 +Bundle-ClassPath: ., + lib/isoparser-1.9.37.jar Bundle-Vendor: Sebastian Annies Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Export-Package: com.sap.sailing.media, +Export-Package: com.sap.sailing.media.mp4, org.mp4parser, org.mp4parser.boxes, org.mp4parser.boxes.iso14496.part12, diff --git a/java/org.mp4parser.isoparser/build.properties b/java/org.mp4parser.isoparser/build.properties index 82266356603..11130baf9b3 100644 --- a/java/org.mp4parser.isoparser/build.properties +++ b/java/org.mp4parser.isoparser/build.properties @@ -1,2 +1,5 @@ bin.includes = META-INF/,\ - lib/isoparser-1.9.31.jar + lib/isoparser-1.9.37.jar +jars.compile.order = . +source.. = src/ +output.. = bin/ diff --git a/java/org.mp4parser.isoparser/lib/isoparser-1.9.37-sources.jar b/java/org.mp4parser.isoparser/lib/isoparser-1.9.37-sources.jar new file mode 100644 index 00000000000..431ea6dc9c9 Binary files /dev/null and b/java/org.mp4parser.isoparser/lib/isoparser-1.9.37-sources.jar differ diff --git a/java/org.mp4parser.isoparser/lib/isoparser-1.9.37.jar b/java/org.mp4parser.isoparser/lib/isoparser-1.9.37.jar new file mode 100644 index 00000000000..edbb431cda3 Binary files /dev/null and b/java/org.mp4parser.isoparser/lib/isoparser-1.9.37.jar differ diff --git a/java/org.mp4parser.isoparser/pom.xml b/java/org.mp4parser.isoparser/pom.xml index 2a3b4240bc3..ca505cbf1a0 100644 --- a/java/org.mp4parser.isoparser/pom.xml +++ b/java/org.mp4parser.isoparser/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT org.mp4parser.isoparser - 1.9.31 + 1.9.37 eclipse-plugin diff --git a/java/org.mp4parser.isoparser/src/com/sap/sailing/media/MediaFileParser.java b/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MP4MediaParser.java old mode 100755 new mode 100644 similarity index 50% rename from java/org.mp4parser.isoparser/src/com/sap/sailing/media/MediaFileParser.java rename to java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MP4MediaParser.java index febe4ff8e6f..9ee6a2d425d --- a/java/org.mp4parser.isoparser/src/com/sap/sailing/media/MediaFileParser.java +++ b/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MP4MediaParser.java @@ -1,9 +1,7 @@ -package com.sap.sailing.media; +package com.sap.sailing.media.mp4; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; import java.util.Date; import javax.xml.parsers.DocumentBuilder; @@ -20,24 +18,24 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -public class MediaFileParser { - public static void main(String[] args) throws MalformedURLException, ParserConfigurationException, SAXException, IOException { - new MediaFileParser().checkMetadata(new File(args[0])); - } - - public void checkMetadata(File file) throws IOException, ParserConfigurationException, SAXException { - boolean spherical = false; - long durationInMillis = -1; - Date recordStartedTimer = null; - try (IsoFile isof = new IsoFile(file)) { - recordStartedTimer = determineRecordingStart(isof); - spherical = determine360(isof); - durationInMillis = determineDurationInMillis(isof); - System.out.println("Start: "+recordStartedTimer.toString()+", duration: "+durationInMillis+"ms; spherical: "+spherical); - } - } +public class MP4MediaParser { + /** + * The relevant size at the start and end of a file, that most likely will contain all metadata + */ + public static final int REQUIRED_SIZE_IN_BYTES = 10000000; - private boolean determine360(IsoFile isof) throws ParserConfigurationException, SAXException, IOException { + /** + * Determine whether or not the provided {@link IsoFile ISO file} contains any "spherical" information within the + * user defined box moov[0]/trak[0]/uuid. + * + * @param isof + * {@link IsoFile ISO file} to analyze + * @return true if spherical information are contained, false otherwise + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + */ + public static boolean determine360(IsoFile isof) throws ParserConfigurationException, SAXException, IOException { boolean spherical = false; UserBox uuidBox = Path.getPath(isof, "moov[0]/trak[0]/uuid"); if (uuidBox != null) { @@ -56,19 +54,36 @@ public class MediaFileParser { return spherical; } - private long determineDurationInMillis(IsoFile isof) { + /** + * Tries to determine the {@link Long duration} of the provided {@link IsoFile ISO file} based on its + * {@link IsoFile#getMovieBox() movie header}. + * + * @param isof + * {@link IsoFile ISO file} to analyze + * @return the determined duration in {@link Long millis}, -1 if the duration could not be determined + */ + public static long determineDurationInMillis(IsoFile isof) { long duration = -1; MovieBox mbox = isof.getMovieBox(); if (mbox != null) { MovieHeaderBox mhb = mbox.getMovieHeaderBox(); if (mhb != null) { - duration = mhb.getDuration()*1000 / mhb.getTimescale(); + duration = mhb.getDuration() * 1000 / mhb.getTimescale(); } } return duration; } - - private Date determineRecordingStart(IsoFile isof) { + + /** + * Tries to determine the {@link Date point in time} when the recording started from the provided {@link IsoFile ISO + * file} based on its {@link IsoFile#getMovieBox() movie header}. + * + * @param isof + * {@link IsoFile ISO file} to analyze + * @return the determined {@link Date point in time} when the recording started, null if the recording + * start could not be determined + */ + public static Date determineRecordingStart(IsoFile isof) { Date creationTime = null; MovieBox mbox = isof.getMovieBox(); if (mbox != null) { diff --git a/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MP4ParserFakeFile.java b/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MP4ParserFakeFile.java new file mode 100644 index 00000000000..e14ceab5486 --- /dev/null +++ b/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MP4ParserFakeFile.java @@ -0,0 +1,169 @@ +package com.sap.sailing.media.mp4; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.file.Files; + +/** + * This class pretends to be a complete larger file, while only carrying the first 10 and last 10 megabytes. It is + * specifically meant to be used for the mp4Parser and only supports the required subset for it. It extends FileChannel + * as the library uses that to skip input for skipped boxes + */ +public class MP4ParserFakeFile extends FileChannel { + private final long totalSizeOfFile; + private long currentPos = 0; + private byte[] startOfFileByteArray; + private byte[] endOfFileByteArray; + + public MP4ParserFakeFile(byte[] start, byte[] end, Long skipped) { + this.startOfFileByteArray = start; + this.endOfFileByteArray = end; + this.totalSizeOfFile = start.length + end.length + skipped; + } + + public MP4ParserFakeFile(File tmp) { + try (SeekableByteChannel inChannel = Files.newByteChannel(tmp.toPath())) { + totalSizeOfFile = tmp.length(); + if (totalSizeOfFile < MP4MediaParser.REQUIRED_SIZE_IN_BYTES * 2) { + throw new IllegalStateException( + "The file is to small to be analysed < " + (MP4MediaParser.REQUIRED_SIZE_IN_BYTES * 2)); + } else { + startOfFileByteArray = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES]; + endOfFileByteArray = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES]; + ByteBuffer startOfFileByteBuffer = ByteBuffer.wrap(startOfFileByteArray); + ByteBuffer endOfFileByteBuffer = ByteBuffer.wrap(endOfFileByteArray); + int startRead = inChannel.read(startOfFileByteBuffer); + if (startRead != startOfFileByteBuffer.capacity()) { + throw new IllegalArgumentException("Could not read fileStart"); + } + inChannel.position(totalSizeOfFile - (MP4MediaParser.REQUIRED_SIZE_IN_BYTES + 1)); + int endRead = inChannel.read(endOfFileByteBuffer); + if (endRead != endOfFileByteBuffer.capacity()) { + throw new IllegalArgumentException("Could not read fileStart"); + } + } + } catch (Exception e) { + // File system error? Re-throwing, cause it cannot be handled here. + throw new RuntimeException(e); + } + } + + @Override + public int read(ByteBuffer dst) throws IOException { + // Ignore boundary crossings into nulled part, as it cannot be parsed in that case. + // Only the actual movie data should be nulled. + int read = 0; + int toRead = dst.remaining(); + final long startOfEndSegment = totalSizeOfFile - (endOfFileByteArray.length + 1); + if (currentPos < startOfFileByteArray.length) { + // in start buffer, read one byte + dst.put(startOfFileByteArray, (int) currentPos, toRead); + read = toRead; + } else if (currentPos < startOfEndSegment) { + throw new IllegalArgumentException( + "The nulled part should only be in the skipped boxes, else reading would be corrupt"); + } else { + int remaining = (int) (totalSizeOfFile - currentPos); + int relative = endOfFileByteArray.length - remaining; + if (currentPos + toRead > totalSizeOfFile) { + toRead = (int) (totalSizeOfFile - currentPos); + } + if (toRead > 0) { + dst.put(endOfFileByteArray, relative, toRead); + read = toRead; + } else { + read = -1; + } + } + currentPos += read; + return read; + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new IllegalArgumentException("MP4 files should only be read"); + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public long position() throws IOException { + return currentPos; + } + + @Override + public FileChannel position(long newPosition) throws IOException { + currentPos = newPosition; + return this; + } + + @Override + public long size() throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public FileChannel truncate(long size) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public void force(boolean metaData) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + throw new IllegalArgumentException("Not supported"); + } + + @Override + protected void implCloseChannel() throws IOException { + } + +} diff --git a/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MediaFileParser.java b/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MediaFileParser.java new file mode 100644 index 00000000000..39b4701ccad --- /dev/null +++ b/java/org.mp4parser.isoparser/src/com/sap/sailing/media/mp4/MediaFileParser.java @@ -0,0 +1,36 @@ +package com.sap.sailing.media.mp4; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.file.Files; +import java.util.Date; + +import javax.xml.parsers.ParserConfigurationException; + +import org.mp4parser.AbstractBoxParser; +import org.mp4parser.IsoFile; +import org.mp4parser.PropertyBoxParserImpl; +import org.xml.sax.SAXException; + +public class MediaFileParser { + public static void main(String[] args) + throws MalformedURLException, ParserConfigurationException, SAXException, IOException { + new MediaFileParser().checkMetadata(new File(args[0])); + } + + public void checkMetadata(File file) throws IOException, ParserConfigurationException, SAXException { + boolean spherical = false; + long durationInMillis = -1; + Date recordStartedTimer = null; + AbstractBoxParser boxParserImpl = new PropertyBoxParserImpl(); + boxParserImpl.skippingBoxes(new String[] { "mdat" }); + try (IsoFile isof = new IsoFile(Files.newByteChannel(file.toPath()), boxParserImpl)) { + recordStartedTimer = MP4MediaParser.determineRecordingStart(isof); + spherical = MP4MediaParser.determine360(isof); + durationInMillis = MP4MediaParser.determineDurationInMillis(isof); + System.out.println("Start: " + recordStartedTimer.toString() + ", duration: " + durationInMillis + + "ms; spherical: " + spherical); + } + } +} diff --git a/wiki/images/aws_automation/activity_diagram.png b/wiki/images/aws_automation/activity_diagram.png new file mode 100644 index 00000000000..e0894213616 Binary files /dev/null and b/wiki/images/aws_automation/activity_diagram.png differ diff --git a/wiki/images/aws_automation/alb_overview.png b/wiki/images/aws_automation/alb_overview.png new file mode 100644 index 00000000000..57093ffd557 Binary files /dev/null and b/wiki/images/aws_automation/alb_overview.png differ diff --git a/wiki/images/aws_automation/sequence_diagram_1.png b/wiki/images/aws_automation/sequence_diagram_1.png new file mode 100644 index 00000000000..d92617dc227 Binary files /dev/null and b/wiki/images/aws_automation/sequence_diagram_1.png differ diff --git a/wiki/images/aws_automation/sequence_diagram_2.png b/wiki/images/aws_automation/sequence_diagram_2.png new file mode 100644 index 00000000000..28fc69215f0 Binary files /dev/null and b/wiki/images/aws_automation/sequence_diagram_2.png differ diff --git a/wiki/info/landscape/aws-automation.md b/wiki/info/landscape/aws-automation.md index 6ed88a081cf..2b8f3062737 100644 --- a/wiki/info/landscape/aws-automation.md +++ b/wiki/info/landscape/aws-automation.md @@ -45,7 +45,7 @@ SERVER_STARTUP_NOTIFY=leon.radeck@sap.com ### 2. SAP instance configuration - + Necessary configuration steps: @@ -78,7 +78,7 @@ To reach the SAP instance by a specific URL (e.g. wcsantander2017.sapsailing.com - Configure the health check of the target group - Register instance within the target group - + Translation: Way of a HTTP/HTTPS request from the load balancer to the sap instance (simplified) @@ -249,7 +249,7 @@ The script is built for the processing of parameters but the functionality is cu User input flow example: - + Translation comment (1): @@ -262,7 +262,7 @@ The user input is assigned to the global variable region. If the input variable is not a text but a type of resource from AWS (e.g. load balancer) the following mechanism will take effect: - + Translation comment (1): @@ -356,7 +356,7 @@ The user can assign values to that variable that are then used as default propos 1. Start EC2 instance 2. Query for its dns name for later ssh connection -3. WWait until ssh connection is established +3. Wait until ssh connection is established 4. Create event and change admin password if necessary 5. Create launch template for replica with user data variable "REPLICATION_CHANNEL" matching to its master