home.md
... ...
@@ -6,6 +6,8 @@ This is the <img src="http://www.sapsailing.com/images/sap-logo_grey.png"/> Wiki
6 6
7 7
Like businesses, sailors need the latest information to make strategic decisions - but they need it even faster. One wrong tack, a false estimation of the current, or the slightest wind shift can cost the skipper the entire race. As premium sponsor of the Kieler Woche 2011, and co-sponsor of Sailing Team Germany (STG), SAP is showing how innovative IT solutions providing real time data analysis can give teams the competitive edge.
8 8
9
+SAP is at the center of today’s technology revolution, developing innovations that not only help businesses run like never before, but also improve the lives of people everywhere. As market leader in enterprise application software, SAP SE (NYSE: SAP) helps companies of all sizes and industries run better. From back office to boardroom, warehouse to storefront, desktop to mobile device – SAP empowers people and organizations to work together more efficiently and use business insight more effectively to stay ahead of the competition. SAP applications and services enable more than 258,000 customers to operate profitably, adapt continuously, and grow sustainably.
10
+
9 11
### Table of Contents
10 12
11 13
* [[Information about this Wiki and HowTo|wiki/howto]]
... ...
@@ -38,6 +40,7 @@ Like businesses, sailors need the latest information to make strategic decisions
38 40
* [[Layout repository|wiki/webdesign]]
39 41
* [[Create boat graphics for the 2D race viewer|wiki/boatGraphicsSVG]]
40 42
* [[Create clickable UI prototypes with Axure|wiki/ui-clickable-prototypes]]
43
+ * [[Uploading Media Content|wiki/uploading-media-content]]
41 44
* General Information
42 45
* [[Architecture and Infrastructure|wiki/architecture-and-infrastructure]]
43 46
* [[Sailing Domain Algorithms|wiki/sailing-domain-algorithms]]
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractBearing.java
... ...
@@ -60,5 +60,10 @@ public abstract class AbstractBearing implements Bearing {
60 60
return object != null && object instanceof Bearing && getDegrees() == ((Bearing) object).getDegrees();
61 61
}
62 62
63
+ @Override
64
+ public double getRadians() {
65
+ return getDegrees() / 180. * Math.PI;
66
+ }
67
+
63 68
64 69
}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java
... ...
@@ -18,6 +18,7 @@ public enum BoatClassMasterdata {
18 18
B_ONE ("B/ONE", true, 7.80, 2.49, BoatHullType.MONOHULL, "B-ONE"),
19 19
DRAGON_INT ("Dragon Int.", true, 8.89, 1.96, BoatHullType.MONOHULL, "Drachen", "Dragon"),
20 20
EXTREME_40 ("Extreme 40", false, 12.2, 6.60, BoatHullType.CATAMARAN, "Extreme-40", "Extreme40", "ESS40"),
21
+ D_35 ("D35", false, 10.81, 6.89, BoatHullType.CATAMARAN),
21 22
EUROPE_INT ("Europe Int.", true, 3.35, 1.35, BoatHullType.MONOHULL, "Europe"),
22 23
F_18 ("Formula 18", true, 6.85, 2.25, BoatHullType.CATAMARAN, "F18", "F-18"),
23 24
FARR_30 ("Farr 30", true, 9.42, 3.08, BoatHullType.MONOHULL, "F30", "F-30", "Farr-30"),
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ImageSize.java
... ...
@@ -0,0 +1,8 @@
1
+package com.sap.sailing.domain.common;
2
+
3
+import java.io.Serializable;
4
+
5
+public interface ImageSize extends Serializable {
6
+ int getWidth();
7
+ int getHeight();
8
+}
... ...
\ No newline at end of file
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java
... ...
@@ -4,5 +4,6 @@ public enum ScoringSchemeType {
4 4
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
5 5
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE,
6 6
HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT,
7
- HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT
7
+ HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT,
8
+ HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT
8 9
}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractTimePoint.java
... ...
@@ -89,7 +89,13 @@ public abstract class AbstractTimePoint implements TimePoint {
89 89
90 90
@Override
91 91
public boolean equals(Object obj) {
92
- return compareTo((TimePoint) obj) == 0;
92
+ if (this == obj) {
93
+ return true;
94
+ } else if (obj instanceof TimePoint) {
95
+ return compareTo((TimePoint) obj) == 0;
96
+ } else {
97
+ return false;
98
+ }
93 99
}
94 100
95 101
@Override
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java
... ...
@@ -20,10 +20,5 @@ public class DegreeBearingImpl extends AbstractBearing implements Bearing {
20 20
public double getDegrees() {
21 21
return bearingDeg;
22 22
}
23
-
24
- @Override
25
- public double getRadians() {
26
- return getDegrees() / 180. * Math.PI;
27
- }
28 23
29 24
}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ImageSizeImpl.java
... ...
@@ -0,0 +1,58 @@
1
+package com.sap.sailing.domain.common.impl;
2
+
3
+import com.sap.sailing.domain.common.ImageSize;
4
+
5
+public class ImageSizeImpl implements ImageSize {
6
+ private static final long serialVersionUID = 1170701774852068780L;
7
+ private int width;
8
+ private int height;
9
+
10
+ ImageSizeImpl() {
11
+ } // for GWT serialization
12
+
13
+ public ImageSizeImpl(int width, int height) {
14
+ super();
15
+ this.width = width;
16
+ this.height = height;
17
+ }
18
+
19
+ @Override
20
+ public int getWidth() {
21
+ return width;
22
+ }
23
+
24
+ @Override
25
+ public int getHeight() {
26
+ return height;
27
+ }
28
+
29
+ @Override
30
+ public String toString() {
31
+ return "(" + getWidth() + "x" + getHeight() + ")";
32
+ }
33
+
34
+ @Override
35
+ public int hashCode() {
36
+ final int prime = 31;
37
+ int result = 1;
38
+ result = prime * result + height;
39
+ result = prime * result + width;
40
+ return result;
41
+ }
42
+
43
+ @Override
44
+ public boolean equals(Object obj) {
45
+ if (this == obj)
46
+ return true;
47
+ if (obj == null)
48
+ return false;
49
+ if (getClass() != obj.getClass())
50
+ return false;
51
+ ImageSizeImpl other = (ImageSizeImpl) obj;
52
+ if (height != other.height)
53
+ return false;
54
+ if (width != other.width)
55
+ return false;
56
+ return true;
57
+ }
58
+}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/MillisecondsDurationImpl.java
... ...
@@ -80,4 +80,21 @@ public class MillisecondsDurationImpl implements Duration {
80 80
long diff = asMillis() - o.asMillis();
81 81
return diff > 0l ? 1 : diff < 0l ? -1 : 0;
82 82
}
83
+
84
+ @Override
85
+ public int hashCode() {
86
+ return (int) (asMillis() & Integer.MAX_VALUE);
87
+ }
88
+
89
+ @Override
90
+ public boolean equals(Object obj) {
91
+ if (this == obj) {
92
+ return true;
93
+ } else if (obj instanceof Duration) {
94
+ return compareTo((Duration) obj) == 0;
95
+ } else {
96
+ return false;
97
+ }
98
+ }
99
+
83 100
}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/media/MediaTrack.java
... ...
@@ -1,7 +1,9 @@
1 1
package com.sap.sailing.domain.common.media;
2 2
3 3
import java.io.Serializable;
4
-import java.util.Date;
4
+
5
+import com.sap.sailing.domain.common.Duration;
6
+import com.sap.sailing.domain.common.TimePoint;
5 7
6 8
/**
7 9
* See http://my.opera.com/core/blog/2010/03/03/everything-you-need-to-know-about-html5-video-and-audio-2
... ...
@@ -69,38 +71,38 @@ public class MediaTrack implements Serializable {
69 71
public String dbId;
70 72
public String title;
71 73
public String url;
72
- public Date startTime;
73
- public int durationInMillis;
74
+ public TimePoint startTime;
75
+ public Duration duration;
74 76
public MimeType mimeType;
75 77
public Status status = Status.UNDEFINED;
76 78
77 79
public MediaTrack() {
78 80
}
79 81
80
- public MediaTrack(String title, String url, Date startTime, int durationInMillis, MimeType mimeType) {
82
+ public MediaTrack(String title, String url, TimePoint startTime, Duration duration, MimeType mimeType) {
81 83
this.title = title;
82 84
this.url = url;
83 85
this.startTime = startTime;
84
- this.durationInMillis = durationInMillis;
86
+ this.duration = duration;
85 87
this.mimeType = mimeType;
86 88
}
87 89
88
- public MediaTrack(String dbId, String title, String url, Date startTime, int durationInMillis, MimeType mimeType) {
90
+ public MediaTrack(String dbId, String title, String url, TimePoint startTime, Duration duration, MimeType mimeType) {
89 91
this.dbId = dbId;
90 92
this.title = title;
91 93
this.url = url;
92 94
this.startTime = startTime;
93
- this.durationInMillis = durationInMillis;
95
+ this.duration = duration;
94 96
this.mimeType = mimeType;
95 97
}
96 98
97 99
public String toString() {
98
- return title + " - " + url + " [" + typeToString() + ']' + startTime + " [" + durationInMillis + status + ']';
100
+ return title + " - " + url + " [" + typeToString() + ']' + startTime + " [" + duration + status + ']';
99 101
}
100 102
101
- public Date deriveEndTime() {
103
+ public TimePoint deriveEndTime() {
102 104
if (startTime != null) {
103
- return new Date(startTime.getTime() + durationInMillis);
105
+ return startTime.plus(duration);
104 106
} else {
105 107
return null;
106 108
}
... ...
@@ -121,11 +123,11 @@ public class MediaTrack implements Serializable {
121 123
* @param startTime Must not be null.
122 124
* @param endTime May be null representing "open end".
123 125
*/
124
- public boolean overlapsWith(Date startTime, Date endTime) {
126
+ public boolean overlapsWith(TimePoint startTime, TimePoint endTime) {
125 127
if (this.startTime == null) {
126 128
return false;
127 129
} else {
128
- return this.deriveEndTime().getTime() > startTime.getTime() && (endTime == null || this.startTime.getTime() < endTime.getTime());
130
+ return this.deriveEndTime().after(startTime) && (endTime == null || this.startTime.before(endTime));
129 131
}
130 132
}
131 133
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/media/MediaUtil.java
... ...
@@ -1,10 +1,10 @@
1 1
package com.sap.sailing.domain.common.media;
2 2
3
-import java.util.Date;
3
+import com.sap.sailing.domain.common.TimePoint;
4 4
5 5
public class MediaUtil {
6 6
7
- public static int compareDatesAllowingNull(Date date1, Date date2) {
7
+ public static int compareDatesAllowingNull(TimePoint date1, TimePoint date2) {
8 8
if (date1 == null) {
9 9
return date2 == null ? 0 : -1;
10 10
} else if (date2 == null) {
... ...
@@ -14,7 +14,7 @@ public class MediaUtil {
14 14
}
15 15
}
16 16
17
- public static boolean equalsDatesAllowingNull(Date date1, Date date2) {
17
+ public static boolean equalsDatesAllowingNull(TimePoint date1, TimePoint date2) {
18 18
if (date1 == null) {
19 19
return date2 == null;
20 20
} else if (date2 == null) {
java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java
... ...
@@ -220,9 +220,13 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
220 220
Speed sog = getSOG(timePoint, sogPair);
221 221
com.sap.sse.common.Util.Pair<COG, COG> cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint);
222 222
Bearing cog = getCOG(timePoint, cogPair);
223
- SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
224
- SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
225
- result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection);
223
+ if (sog != null && cog != null) {
224
+ SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
225
+ SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
226
+ result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection);
227
+ } else {
228
+ result = null;
229
+ }
226 230
} else {
227 231
result = null;
228 232
}
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/DBMediaTrack.java
... ...
@@ -1,23 +1,25 @@
1 1
package com.sap.sailing.domain.persistence.media;
2 2
3
-import java.util.Date;
3
+import com.sap.sailing.domain.common.Duration;
4
+import com.sap.sailing.domain.common.TimePoint;
4 5
5 6
public class DBMediaTrack {
6 7
7 8
public final String dbId;
8 9
public final String title;
9 10
public final String url;
10
- public final Date startTime;
11
- public final int durationInMillis;
11
+ public final TimePoint startTime;
12
+ public final Duration duration;
12 13
public final String mimeType;
13 14
14
- public DBMediaTrack(String dbId, String title, String url, Date startTime, int duration, String mimeType) {
15
+ public DBMediaTrack(String dbId, String title, String url, TimePoint startTime, Duration duration, String mimeType) {
15 16
super();
16 17
this.dbId = dbId;
17 18
this.title = title;
18 19
this.url = url;
19 20
this.startTime = startTime;
20
- this.durationInMillis = duration;
21
+ this.duration = duration;
21 22
this.mimeType = mimeType;
22 23
}
24
+
23 25
}
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/MediaDB.java
... ...
@@ -1,9 +1,11 @@
1 1
package com.sap.sailing.domain.persistence.media;
2 2
3 3
import java.util.Collections;
4
-import java.util.Date;
5 4
import java.util.List;
6 5
6
+import com.sap.sailing.domain.common.Duration;
7
+import com.sap.sailing.domain.common.TimePoint;
8
+
7 9
/**
8 10
* Offers CRUD methods for mongo representation of media track objects.
9 11
*
... ...
@@ -15,7 +17,7 @@ public interface MediaDB {
15 17
/**
16 18
* Stores a new track to the database, returning the db-generated id.
17 19
*/
18
- String insertMediaTrack(String title, String url, Date startTime, int durationInMillis, String mimeType);
20
+ String insertMediaTrack(String title, String url, TimePoint startTime, Duration duration, String mimeType);
19 21
20 22
/**
21 23
* Stores a new track to the database, using the db id of the specified trackToImport.
... ...
@@ -23,7 +25,7 @@ public interface MediaDB {
23 25
* @throws NullpointerException When trackToImport.dbId is null.
24 26
* @throws IllegalArgumentException When track with specified dbId already exists.
25 27
*/
26
- void insertMediaTrackWithId(String dbId, String videoTitle, String url, Date startTime, int durationInMillis, String mimeType);
28
+ void insertMediaTrackWithId(String dbId, String videoTitle, String url, TimePoint startTime, Duration duration, String mimeType);
27 29
28 30
List<DBMediaTrack> loadAllMediaTracks();
29 31
... ...
@@ -33,19 +35,19 @@ public interface MediaDB {
33 35
34 36
void updateUrl(String dbId, String url);
35 37
36
- void updateStartTime(String dbId, Date startTime);
38
+ void updateStartTime(String dbId, TimePoint startTime);
37 39
38
- void updateDuration(String dbId, int durationInMillis);
40
+ void updateDuration(String dbId, Duration duration);
39 41
40 42
MediaDB TEST_STUB = new MediaDB() {
41 43
42 44
@Override
43
- public String insertMediaTrack(String title, String url, Date startTime, int durationInMillis, String mimeType) {
45
+ public String insertMediaTrack(String title, String url, TimePoint startTime, Duration duration, String mimeType) {
44 46
return "0";
45 47
}
46 48
47 49
@Override
48
- public void insertMediaTrackWithId(String dbId, String videoTitle, String url, Date startTime, int durationInMillis, String mimeType) {};
50
+ public void insertMediaTrackWithId(String dbId, String videoTitle, String url, TimePoint startTime, Duration duration, String mimeType) {};
49 51
50 52
@Override
51 53
public List<DBMediaTrack> loadAllMediaTracks() {
... ...
@@ -62,10 +64,10 @@ public interface MediaDB {
62 64
public void updateUrl(String dbId, String url) {}
63 65
64 66
@Override
65
- public void updateStartTime(String dbId, Date startTime) {}
67
+ public void updateStartTime(String dbId, TimePoint startTime) {}
66 68
67 69
@Override
68
- public void updateDuration(String dbId, int durationInMillis) {}
70
+ public void updateDuration(String dbId, Duration duration) {}
69 71
70 72
};
71 73
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/media/impl/MediaDBImpl.java
... ...
@@ -13,6 +13,10 @@ import com.mongodb.DBCursor;
13 13
import com.mongodb.DBObject;
14 14
import com.mongodb.MongoException;
15 15
import com.mongodb.WriteConcern;
16
+import com.sap.sailing.domain.common.Duration;
17
+import com.sap.sailing.domain.common.TimePoint;
18
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
19
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
16 20
import com.sap.sailing.domain.persistence.media.DBMediaTrack;
17 21
import com.sap.sailing.domain.persistence.media.MediaDB;
18 22
... ...
@@ -39,12 +43,12 @@ public class MediaDBImpl implements MediaDB {
39 43
}
40 44
41 45
@Override
42
- public String insertMediaTrack(String title, String url, Date startTime, int durationInMillis, String mimeType) {
46
+ public String insertMediaTrack(String title, String url, TimePoint startTime, Duration duration, String mimeType) {
43 47
BasicDBObject dbMediaTrack = new BasicDBObject();
44 48
dbMediaTrack.put(DbNames.Fields.MEDIA_TITLE.name(), title);
45 49
dbMediaTrack.put(DbNames.Fields.MEDIA_URL.name(), url);
46
- dbMediaTrack.put(DbNames.Fields.STARTTIME.name(), startTime);
47
- dbMediaTrack.put(DbNames.Fields.DURATION_IN_MILLIS.name(), durationInMillis);
50
+ dbMediaTrack.put(DbNames.Fields.STARTTIME.name(), startTime == null ? null : startTime.asDate());
51
+ dbMediaTrack.put(DbNames.Fields.DURATION_IN_MILLIS.name(), duration == null ? null : duration.asMillis());
48 52
dbMediaTrack.put(DbNames.Fields.MIME_TYPE.name(), mimeType);
49 53
DBCollection dbVideos = getVideoCollection();
50 54
dbVideos.insert(dbMediaTrack);
... ...
@@ -52,13 +56,13 @@ public class MediaDBImpl implements MediaDB {
52 56
}
53 57
54 58
@Override
55
- public void insertMediaTrackWithId(String dbId, String title, String url, Date startTime, int durationInMillis, String mimeType) {
59
+ public void insertMediaTrackWithId(String dbId, String title, String url, TimePoint startTime, Duration duration, String mimeType) {
56 60
BasicDBObject dbMediaTrack = new BasicDBObject();
57 61
dbMediaTrack.put(DbNames.Fields._id.name(), new ObjectId(dbId));
58 62
dbMediaTrack.put(DbNames.Fields.MEDIA_TITLE.name(), title);
59 63
dbMediaTrack.put(DbNames.Fields.MEDIA_URL.name(), url);
60
- dbMediaTrack.put(DbNames.Fields.STARTTIME.name(), startTime);
61
- dbMediaTrack.put(DbNames.Fields.DURATION_IN_MILLIS.name(), durationInMillis);
64
+ dbMediaTrack.put(DbNames.Fields.STARTTIME.name(), startTime == null ? null : startTime.asDate());
65
+ dbMediaTrack.put(DbNames.Fields.DURATION_IN_MILLIS.name(), duration == null ? null : duration.asMillis());
62 66
dbMediaTrack.put(DbNames.Fields.MIME_TYPE.name(), mimeType);
63 67
DBCollection dbVideos = getVideoCollection();
64 68
try {
... ...
@@ -85,9 +89,12 @@ public class MediaDBImpl implements MediaDB {
85 89
String title = (String) dbObject.get(DbNames.Fields.MEDIA_TITLE.name());
86 90
String url = (String) dbObject.get(DbNames.Fields.MEDIA_URL.name());
87 91
Date startTime = (Date) dbObject.get(DbNames.Fields.STARTTIME.name());
88
- Integer durationInMillis = (Integer) dbObject.get(DbNames.Fields.DURATION_IN_MILLIS.name());
92
+ Long duration = (Long) dbObject.get(DbNames.Fields.DURATION_IN_MILLIS.name());
89 93
String mimeType = (String) dbObject.get(DbNames.Fields.MIME_TYPE.name());
90
- DBMediaTrack dbMediaTrack = new DBMediaTrack(dbId, title, url, startTime, durationInMillis == null ? 0 : durationInMillis, mimeType);
94
+ DBMediaTrack dbMediaTrack = new DBMediaTrack(dbId, title, url,
95
+ startTime == null ? null : new MillisecondsTimePoint(startTime),
96
+ duration == null ? null : new MillisecondsDurationImpl(duration),
97
+ mimeType);
91 98
return dbMediaTrack;
92 99
}
93 100
... ...
@@ -131,23 +138,23 @@ public class MediaDBImpl implements MediaDB {
131 138
}
132 139
133 140
@Override
134
- public void updateStartTime(String dbId, Date startTime) {
141
+ public void updateStartTime(String dbId, TimePoint startTime) {
135 142
BasicDBObject updateQuery = new BasicDBObject();
136 143
updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId));
137 144
138 145
BasicDBObject updateCommand = new BasicDBObject();
139
- updateCommand.append("$set", new BasicDBObject(DbNames.Fields.STARTTIME.name(), startTime));
146
+ updateCommand.append("$set", new BasicDBObject(DbNames.Fields.STARTTIME.name(), startTime == null ? null : startTime.asDate()));
140 147
141 148
getVideoCollection().update(updateQuery, updateCommand);
142 149
}
143 150
144 151
@Override
145
- public void updateDuration(String dbId, int durationInMillis) {
152
+ public void updateDuration(String dbId, Duration duration) {
146 153
BasicDBObject updateQuery = new BasicDBObject();
147 154
updateQuery.append(DbNames.Fields._id.name(), new ObjectId(dbId));
148 155
149 156
BasicDBObject updateCommand = new BasicDBObject();
150
- updateCommand.append("$set", new BasicDBObject(DbNames.Fields.DURATION_IN_MILLIS.name(), durationInMillis));
157
+ updateCommand.append("$set", new BasicDBObject(DbNames.Fields.DURATION_IN_MILLIS.name(), duration == null ? null : duration.asMillis()));
151 158
152 159
getVideoCollection().update(updateQuery, updateCommand);
153 160
}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java
... ...
@@ -1,7 +1,9 @@
1 1
package com.sap.sailing.domain.base;
2 2
3 3
import java.net.URL;
4
+import java.util.concurrent.ExecutionException;
4 5
6
+import com.sap.sailing.domain.common.ImageSize;
5 7
import com.sap.sailing.domain.common.Renamable;
6 8
import com.sap.sailing.domain.common.TimePoint;
7 9
import com.sap.sailing.domain.common.WithID;
... ...
@@ -61,6 +63,14 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID {
61 63
62 64
void removeSponsorImageURL(URL sponsorImageURL);
63 65
66
+ /**
67
+ * Replaces the {@link #getSponsorImageURLs() current contents of the sponsorship image URL sequence} by the image URLs in
68
+ * <code>sponsorImageURLs</code>.
69
+ *
70
+ * @param sponsorImageURLs
71
+ * if <code>null</code>, the internal sequence of sponsorship image URLs is cleared but remains valid (non-
72
+ * <code>null</code>)
73
+ */
64 74
void setSponsorImageURLs(Iterable<URL> sponsorImageURLs);
65 75
66 76
/**
... ...
@@ -111,4 +121,10 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID {
111 121
void setOfficialWebsiteURL(URL officialWebsiteURL);
112 122
113 123
Iterable<? extends LeaderboardGroupBase> getLeaderboardGroups();
124
+
125
+ /**
126
+ * For the images references by the image URLs in {@link #getImageURLs()}, {@link #getSponsorImageURLs()} and {@link #getLogoImageURL()}
127
+ * determines the image dimensions.
128
+ */
129
+ ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException;
114 130
}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java
... ...
@@ -146,7 +146,9 @@ public abstract class EventBaseImpl implements EventBase {
146 146
@Override
147 147
public void setImageURLs(Iterable<URL> imageURLs) {
148 148
this.imageURLs.clear();
149
- Util.addAll(imageURLs, this.imageURLs);
149
+ if (imageURLs != null) {
150
+ Util.addAll(imageURLs, this.imageURLs);
151
+ }
150 152
}
151 153
152 154
@Override
... ...
@@ -169,7 +171,9 @@ public abstract class EventBaseImpl implements EventBase {
169 171
@Override
170 172
public void setVideoURLs(Iterable<URL> videoURLs) {
171 173
this.videoURLs.clear();
172
- Util.addAll(videoURLs, this.videoURLs);
174
+ if (videoURLs != null) {
175
+ Util.addAll(videoURLs, this.videoURLs);
176
+ }
173 177
}
174 178
175 179
@Override
... ...
@@ -192,7 +196,9 @@ public abstract class EventBaseImpl implements EventBase {
192 196
@Override
193 197
public void setSponsorImageURLs(Iterable<URL> sponsorImageURLs) {
194 198
this.sponsorImageURLs.clear();
195
- Util.addAll(sponsorImageURLs, this.sponsorImageURLs);
199
+ if (sponsorImageURLs != null) {
200
+ Util.addAll(sponsorImageURLs, this.sponsorImageURLs);
201
+ }
196 202
}
197 203
198 204
@Override
... ...
@@ -214,4 +220,5 @@ public abstract class EventBaseImpl implements EventBase {
214 220
public void setOfficialWebsiteURL(URL officialWebsiteURL) {
215 221
this.officialWebsiteURL = officialWebsiteURL;
216 222
}
223
+
217 224
}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java
... ...
@@ -1,15 +1,21 @@
1 1
package com.sap.sailing.domain.base.impl;
2 2
3
+import java.net.URL;
4
+import java.util.HashMap;
5
+import java.util.Map;
3 6
import java.util.UUID;
7
+import java.util.concurrent.ExecutionException;
4 8
5 9
import com.sap.sailing.domain.base.EventBase;
6 10
import com.sap.sailing.domain.base.LeaderboardGroupBase;
7 11
import com.sap.sailing.domain.base.Venue;
12
+import com.sap.sailing.domain.common.ImageSize;
8 13
import com.sap.sailing.domain.common.TimePoint;
9 14
10 15
/**
11 16
* A simplified implementation of the {@link EventBase} interface which maintains an immutable collection of
12
- * {@link LeaderboardGroupBase} objects to implement the {@link #getLeaderboardGroups()} method.
17
+ * {@link LeaderboardGroupBase} objects to implement the {@link #getLeaderboardGroups()} method. A local image
18
+ * size cache can be maintained using the {@link #setImageSize} method.
13 19
*
14 20
* @author Axel Uhl (D043530)
15 21
*
... ...
@@ -17,21 +23,31 @@ import com.sap.sailing.domain.common.TimePoint;
17 23
public class StrippedEventImpl extends EventBaseImpl {
18 24
private static final long serialVersionUID = 5608501747499933988L;
19 25
private final Iterable<LeaderboardGroupBase> leaderboardGroups;
26
+ private final Map<URL, ImageSize> imageSizes;
20 27
21 28
public StrippedEventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName,
22 29
boolean isPublic, UUID id, Iterable<LeaderboardGroupBase> leaderboardGroups) {
23
- super(name, startDate, endDate, venueName, isPublic, id);
24
- this.leaderboardGroups = leaderboardGroups;
30
+ this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id, leaderboardGroups);
25 31
}
26 32
27 33
public StrippedEventImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue,
28 34
boolean isPublic, UUID id, Iterable<LeaderboardGroupBase> leaderboardGroups) {
29 35
super(name, startDate, endDate, venue, isPublic, id);
30 36
this.leaderboardGroups = leaderboardGroups;
37
+ this.imageSizes = new HashMap<URL, ImageSize>();
31 38
}
32 39
33 40
@Override
34 41
public Iterable<LeaderboardGroupBase> getLeaderboardGroups() {
35 42
return leaderboardGroups;
36 43
}
44
+
45
+ public void setImageSize(URL imageURL, ImageSize imageSize) {
46
+ imageSizes.put(imageURL, imageSize);
47
+ }
48
+
49
+ @Override
50
+ public ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException {
51
+ return imageSizes.get(imageURL);
52
+ }
37 53
}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/ConcurrentHashBag.java
... ...
@@ -0,0 +1,92 @@
1
+package com.sap.sailing.util.impl;
2
+
3
+import java.util.AbstractCollection;
4
+import java.util.Map.Entry;
5
+import java.util.concurrent.ConcurrentHashMap;
6
+
7
+/**
8
+ * An "almost" thread-safe bag implementation. The constraint: when for equal objects an add or remove is performed,
9
+ * there should be no concurrency regarding that key. The implementation uses a {@link ConcurrentHashMap} internally.
10
+ *
11
+ * @author Axel Uhl (D043530)
12
+ */
13
+public class ConcurrentHashBag<T> extends AbstractCollection<T> {
14
+ private ConcurrentHashMap<T, Integer> map = new ConcurrentHashMap<T, Integer>();
15
+ private int size;
16
+
17
+ @Override
18
+ public boolean contains(Object o) {
19
+ return map.containsKey(o);
20
+ }
21
+
22
+ @Override
23
+ public boolean remove(Object o) {
24
+ @SuppressWarnings("unchecked")
25
+ T t = (T) o;
26
+ Integer oldCount = map.remove(t);
27
+ if (oldCount != null && oldCount != 1) {
28
+ map.put(t, oldCount - 1);
29
+ }
30
+ if (oldCount != null) {
31
+ size--;
32
+ }
33
+ return oldCount != null;
34
+ }
35
+
36
+ @Override
37
+ public boolean add(T e) {
38
+ Integer oldCount = map.put(e, 1);
39
+ if (oldCount != null && oldCount != 0) {
40
+ map.put(e, oldCount + 1);
41
+ }
42
+ size++;
43
+ return true;
44
+ }
45
+
46
+ @Override
47
+ public java.util.Iterator<T> iterator() {
48
+ return new Iterator();
49
+ }
50
+
51
+ @Override
52
+ public int size() {
53
+ return size;
54
+ }
55
+
56
+ @Override
57
+ public boolean isEmpty() {
58
+ return map.isEmpty();
59
+ }
60
+
61
+ private class Iterator implements java.util.Iterator<T> {
62
+ private final java.util.Iterator<Entry<T, Integer>> iter = map.entrySet().iterator();
63
+ private int howManyMore = 0;
64
+ private T lastElementOfWhichWeHaveMore;
65
+
66
+ @Override
67
+ public boolean hasNext() {
68
+ return howManyMore > 0 || iter.hasNext();
69
+ }
70
+
71
+ @Override
72
+ public T next() {
73
+ final T result;
74
+ if (howManyMore > 0) {
75
+ result = lastElementOfWhichWeHaveMore;
76
+ howManyMore--;
77
+ } else {
78
+ Entry<T, Integer> next = iter.next();
79
+ howManyMore = next.getValue()-1;
80
+ lastElementOfWhichWeHaveMore = next.getKey();
81
+ result = next.getKey();
82
+ }
83
+ return result;
84
+ }
85
+
86
+ @Override
87
+ public void remove() {
88
+ // TODO Auto-generated method stub
89
+
90
+ }
91
+ }
92
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/LockUtil.java
... ...
@@ -2,7 +2,6 @@ package com.sap.sailing.util.impl;
2 2
3 3
import java.util.HashMap;
4 4
import java.util.HashSet;
5
-import java.util.List;
6 5
import java.util.Map;
7 6
import java.util.Set;
8 7
import java.util.concurrent.ConcurrentHashMap;
... ...
@@ -381,7 +380,7 @@ public class LockUtil {
381 380
// capture the stack traces as quickly as possible to try to reflect the situation as it was when the lock couuldn't be obtained
382 381
StackTraceElement[] writerStackTrace = writer != null ? writer.getStackTrace() : null;
383 382
Map<Thread, StackTraceElement[]> readerStackTraces = new HashMap<Thread, StackTraceElement[]>();
384
- final List<Thread> readers = lockParent.getReaders();
383
+ final Iterable<Thread> readers = lockParent.getReaders();
385 384
for (Thread reader : readers) {
386 385
readerStackTraces.put(reader, reader.getStackTrace());
387 386
}
... ...
@@ -396,7 +395,7 @@ public class LockUtil {
396 395
message.append("\nThe current writer is:\n");
397 396
appendThreadData(message, writer, writerStackTrace);
398 397
}
399
- if (readers != null && !readers.isEmpty()) {
398
+ if (readers != null && !Util.isEmpty(readers)) {
400 399
message.append("\nThe current readers are:\n");
401 400
for (Thread reader : readers) {
402 401
appendThreadData(message, reader, readerStackTraces.get(reader));
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/util/impl/NamedReentrantReadWriteLock.java
... ...
@@ -3,8 +3,6 @@ package com.sap.sailing.util.impl;
3 3
import java.io.IOException;
4 4
import java.io.ObjectInputStream;
5 5
import java.util.ArrayList;
6
-import java.util.Collections;
7
-import java.util.List;
8 6
import java.util.concurrent.TimeUnit;
9 7
import java.util.concurrent.locks.Condition;
10 8
import java.util.concurrent.locks.ReentrantReadWriteLock;
... ...
@@ -35,7 +33,7 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen
35 33
private final String writeLockName;
36 34
private final WriteLockWrapper writeLockWrapper;
37 35
private final ReadLockWrapper readLockWrapper;
38
- private transient List<Thread> readers;
36
+ private transient ConcurrentHashBag<Thread> readers;
39 37
40 38
private class WriteLockWrapper extends WriteLock {
41 39
private static final long serialVersionUID = -4234819025137348944L;
... ...
@@ -110,7 +108,8 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen
110 108
public void lockInterruptibly() throws InterruptedException {
111 109
try {
112 110
readLock.lockInterruptibly();
113
- readers.add(Thread.currentThread());
111
+ final Thread currentThread = Thread.currentThread();
112
+ readers.add(currentThread);
114 113
} catch (InterruptedException ie) {
115 114
throw ie;
116 115
}
... ...
@@ -120,7 +119,8 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen
120 119
public boolean tryLock() {
121 120
boolean result = readLock.tryLock();
122 121
if (result) {
123
- readers.add(Thread.currentThread());
122
+ final Thread currentThread = Thread.currentThread();
123
+ readers.add(currentThread);
124 124
}
125 125
return result;
126 126
}
... ...
@@ -129,7 +129,8 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen
129 129
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
130 130
boolean result = readLock.tryLock(timeout, unit);
131 131
if (result) {
132
- readers.add(Thread.currentThread());
132
+ final Thread currentThread = Thread.currentThread();
133
+ readers.add(currentThread);
133 134
}
134 135
return result;
135 136
}
... ...
@@ -159,12 +160,12 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen
159 160
this.writeLockName = "writeLock "+name;
160 161
this.writeLockWrapper = new WriteLockWrapper(super.writeLock());
161 162
this.readLockWrapper = new ReadLockWrapper(super.readLock());
162
- this.readers = Collections.synchronizedList(new ArrayList<Thread>());
163
+ this.readers = new ConcurrentHashBag<Thread>();
163 164
}
164 165
165 166
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
166 167
ois.defaultReadObject();
167
- this.readers = Collections.synchronizedList(new ArrayList<Thread>());
168
+ this.readers = new ConcurrentHashBag<Thread>();
168 169
}
169 170
170 171
@Override
... ...
@@ -184,9 +185,9 @@ public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implemen
184 185
185 186
/**
186 187
* Contains the threads currently holding a read lock. Each thread is contained as many times as it
187
- * successfully acquired the read lock re-entrantly.
188
+ * successfully acquired the read lock re-entrantly. The result is a snapshot that is not live.
188 189
*/
189
- public List<Thread> getReaders() {
190
+ public Iterable<Thread> getReaders() {
190 191
return new ArrayList<Thread>(readers);
191 192
}
192 193
java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SailMasterConnectorImpl.java
... ...
@@ -27,6 +27,7 @@ import java.util.logging.Level;
27 27
import java.util.logging.Logger;
28 28
29 29
import com.sap.sailing.domain.base.BoatClass;
30
+import com.sap.sailing.domain.common.AbstractBearing;
30 31
import com.sap.sailing.domain.common.Distance;
31 32
import com.sap.sailing.domain.common.Position;
32 33
import com.sap.sailing.domain.common.Speed;
... ...
@@ -388,7 +389,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
388 389
final Speed velocityMadeGood = fixSections[vmgIndex].trim().length() == 0 ? null : new KnotSpeedImpl(
389 390
Double.valueOf(fixSections[vmgIndex]));
390 391
fixDetailIndex += 2;
391
- final DegreeBearingImpl cog = new DegreeBearingImpl(
392
+ final AbstractBearing cog = new DegreeBearingImpl(
392 393
Double.valueOf(fixSections[fixDetailIndex++]));
393 394
final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(speedOverGroundInKnots, cog);
394 395
final Integer nextMarkIndex = fixSections.length <= fixDetailIndex
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaTrackTest.java
... ...
@@ -1,24 +1,25 @@
1 1
package com.sap.sailing.domain.common;
2 2
3
-import static org.junit.Assert.assertFalse;
4
-import static org.junit.Assert.assertTrue;
5
-
6
-import java.util.Date;
7
-
8 3
import org.junit.Test;
9 4
5
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
6
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
10 7
import com.sap.sailing.domain.common.media.MediaTrack;
11 8
9
+import static org.junit.Assert.*;
10
+
12 11
public class MediaTrackTest {
13 12
13
+ private static final Duration ONE_MILLISECOND = new MillisecondsDurationImpl(1);
14
+
14 15
@Test
15 16
public void testExactOverlap() throws Exception {
16 17
MediaTrack mediaTrack = new MediaTrack();
17
- mediaTrack.startTime = new Date();
18
- mediaTrack.durationInMillis = 1;
18
+ mediaTrack.startTime = MillisecondsTimePoint.now();
19
+ mediaTrack.duration = ONE_MILLISECOND;
19 20
20
- Date startTime = new Date(mediaTrack.startTime.getTime());
21
- Date endTime = new Date(mediaTrack.deriveEndTime().getTime());
21
+ TimePoint startTime = mediaTrack.startTime;
22
+ TimePoint endTime = mediaTrack.deriveEndTime();
22 23
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
23 24
24 25
}
... ...
@@ -26,11 +27,11 @@ public class MediaTrackTest {
26 27
@Test
27 28
public void testNoOverlapLeft() throws Exception {
28 29
MediaTrack mediaTrack = new MediaTrack();
29
- mediaTrack.startTime = new Date();
30
- mediaTrack.durationInMillis = 1;
30
+ mediaTrack.startTime = MillisecondsTimePoint.now();
31
+ mediaTrack.duration = ONE_MILLISECOND;
31 32
32
- Date startTime = new Date(mediaTrack.startTime.getTime() + 2);
33
- Date endTime = new Date(startTime.getTime() + 1);
33
+ TimePoint startTime = mediaTrack.startTime.plus(2);
34
+ TimePoint endTime = startTime.plus(1);
34 35
assertFalse(mediaTrack.overlapsWith(startTime, endTime));
35 36
36 37
}
... ...
@@ -38,11 +39,11 @@ public class MediaTrackTest {
38 39
@Test
39 40
public void testNoOverlapRight() throws Exception {
40 41
MediaTrack mediaTrack = new MediaTrack();
41
- mediaTrack.startTime = new Date();
42
- mediaTrack.durationInMillis = 1;
42
+ mediaTrack.startTime = MillisecondsTimePoint.now();
43
+ mediaTrack.duration = ONE_MILLISECOND;
43 44
44
- Date startTime = new Date(mediaTrack.startTime.getTime() - 2);
45
- Date endTime = new Date(startTime.getTime() + 1);
45
+ TimePoint startTime = mediaTrack.startTime.minus( 2);
46
+ TimePoint endTime = startTime.plus(1);
46 47
assertFalse(mediaTrack.overlapsWith(startTime, endTime));
47 48
48 49
}
... ...
@@ -50,11 +51,11 @@ public class MediaTrackTest {
50 51
@Test
51 52
public void testPartialOverlapLeft() throws Exception {
52 53
MediaTrack mediaTrack = new MediaTrack();
53
- mediaTrack.startTime = new Date();
54
- mediaTrack.durationInMillis = 2;
54
+ mediaTrack.startTime = MillisecondsTimePoint.now();
55
+ mediaTrack.duration = ONE_MILLISECOND.times(2);
55 56
56
- Date startTime = new Date(mediaTrack.startTime.getTime() - 1);
57
- Date endTime = new Date(startTime.getTime() + 2);
57
+ TimePoint startTime = mediaTrack.startTime.minus( 1);
58
+ TimePoint endTime = startTime.plus(2);
58 59
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
59 60
60 61
}
... ...
@@ -62,11 +63,11 @@ public class MediaTrackTest {
62 63
@Test
63 64
public void testPartialOverlapRight() throws Exception {
64 65
MediaTrack mediaTrack = new MediaTrack();
65
- mediaTrack.startTime = new Date();
66
- mediaTrack.durationInMillis = 2;
66
+ mediaTrack.startTime = MillisecondsTimePoint.now();
67
+ mediaTrack.duration = ONE_MILLISECOND.times(2);
67 68
68
- Date startTime = new Date(mediaTrack.startTime.getTime() + 1);
69
- Date endTime = new Date(startTime.getTime() + 2);
69
+ TimePoint startTime = mediaTrack.startTime.plus(1);
70
+ TimePoint endTime = startTime.plus(2);
70 71
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
71 72
72 73
}
... ...
@@ -74,11 +75,11 @@ public class MediaTrackTest {
74 75
@Test
75 76
public void testMediaFullyIncluded() throws Exception {
76 77
MediaTrack mediaTrack = new MediaTrack();
77
- mediaTrack.startTime = new Date();
78
- mediaTrack.durationInMillis = 1;
78
+ mediaTrack.startTime = MillisecondsTimePoint.now();
79
+ mediaTrack.duration = ONE_MILLISECOND;
79 80
80
- Date startTime = new Date(mediaTrack.startTime.getTime() - 1);
81
- Date endTime = new Date(startTime.getTime() + 3);
81
+ TimePoint startTime = mediaTrack.startTime.minus( 1);
82
+ TimePoint endTime = startTime.plus(3);
82 83
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
83 84
84 85
}
... ...
@@ -86,11 +87,11 @@ public class MediaTrackTest {
86 87
@Test
87 88
public void testMediaFullyIncluding() throws Exception {
88 89
MediaTrack mediaTrack = new MediaTrack();
89
- mediaTrack.startTime = new Date();
90
- mediaTrack.durationInMillis = 3;
90
+ mediaTrack.startTime = MillisecondsTimePoint.now();
91
+ mediaTrack.duration = ONE_MILLISECOND.times(3);
91 92
92
- Date startTime = new Date(mediaTrack.startTime.getTime() + 1);
93
- Date endTime = new Date(startTime.getTime() + 1);
93
+ TimePoint startTime = mediaTrack.startTime.plus(1);
94
+ TimePoint endTime = startTime.plus(1);
94 95
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
95 96
96 97
}
... ...
@@ -98,11 +99,11 @@ public class MediaTrackTest {
98 99
@Test
99 100
public void testOverlapOpenEndStartingEarlier() throws Exception {
100 101
MediaTrack mediaTrack = new MediaTrack();
101
- mediaTrack.startTime = new Date();
102
- mediaTrack.durationInMillis = 1;
102
+ mediaTrack.startTime = MillisecondsTimePoint.now();
103
+ mediaTrack.duration = ONE_MILLISECOND;
103 104
104
- Date startTime = new Date(mediaTrack.startTime.getTime() - 1);
105
- Date endTime = null; //--> open end
105
+ TimePoint startTime = mediaTrack.startTime.minus( 1);
106
+ TimePoint endTime = null; //--> open end
106 107
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
107 108
108 109
}
... ...
@@ -110,11 +111,11 @@ public class MediaTrackTest {
110 111
@Test
111 112
public void testOverlapOpenEndStartingLater() throws Exception {
112 113
MediaTrack mediaTrack = new MediaTrack();
113
- mediaTrack.startTime = new Date();
114
- mediaTrack.durationInMillis = 2;
114
+ mediaTrack.startTime = MillisecondsTimePoint.now();
115
+ mediaTrack.duration = ONE_MILLISECOND.times(2);
115 116
116
- Date startTime = new Date(mediaTrack.startTime.getTime() + 1);
117
- Date endTime = null; //--> open end
117
+ TimePoint startTime = mediaTrack.startTime.plus(1);
118
+ TimePoint endTime = null; //--> open end
118 119
assertTrue(mediaTrack.overlapsWith(startTime, endTime));
119 120
120 121
}
... ...
@@ -122,11 +123,11 @@ public class MediaTrackTest {
122 123
@Test
123 124
public void testOpenEndNoOverlap() throws Exception {
124 125
MediaTrack mediaTrack = new MediaTrack();
125
- mediaTrack.startTime = new Date();
126
- mediaTrack.durationInMillis = 1;
126
+ mediaTrack.startTime = MillisecondsTimePoint.now();
127
+ mediaTrack.duration = ONE_MILLISECOND;
127 128
128
- Date startTime = new Date(mediaTrack.startTime.getTime() + 2);
129
- Date endTime = null; //--> open end
129
+ TimePoint startTime = mediaTrack.startTime.plus(2);
130
+ TimePoint endTime = null; //--> open end
130 131
assertFalse(mediaTrack.overlapsWith(startTime, endTime));
131 132
132 133
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/MediaUtilTest.java
... ...
@@ -1,83 +1,81 @@
1 1
package com.sap.sailing.domain.common;
2 2
3
-import static org.junit.Assert.assertFalse;
4
-import static org.junit.Assert.assertTrue;
5
-
6
-import java.util.Date;
7
-
8 3
import org.junit.Test;
9 4
5
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
10 6
import com.sap.sailing.domain.common.media.MediaUtil;
11 7
8
+import static org.junit.Assert.*;
9
+
12 10
public class MediaUtilTest {
13 11
14 12
@Test
15 13
public void testCompareDatesAllNull() throws Exception {
16
- Date date1 = null;
17
- Date date2 = null;
14
+ TimePoint date1 = null;
15
+ TimePoint date2 = null;
18 16
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) == 0);
19 17
}
20 18
21 19
@Test
22 20
public void testCompareDatesFirstNull() throws Exception {
23
- Date date1 = null;
24
- Date date2 = new Date();
21
+ TimePoint date1 = null;
22
+ TimePoint date2 = MillisecondsTimePoint.now();
25 23
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) < 0);
26 24
}
27 25
28 26
@Test
29 27
public void testCompareDatesSecondNull() throws Exception {
30
- Date date1 = new Date();
31
- Date date2 = null;
28
+ TimePoint date1 = MillisecondsTimePoint.now();
29
+ TimePoint date2 = null;
32 30
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) > 0);
33 31
}
34 32
35 33
@Test
36 34
public void testCompareDatesFirstGreaterSecond() throws Exception {
37
- Date date1 = new Date();
38
- Date date2 = new Date(date1.getTime() - 1);
35
+ TimePoint date1 = MillisecondsTimePoint.now();
36
+ TimePoint date2 = date1.minus(1);
39 37
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) > 0);
40 38
}
41 39
42 40
@Test
43 41
public void testCompareDatesSecondGreaterFirst() throws Exception {
44
- Date date1 = new Date();
45
- Date date2 = new Date(date1.getTime() + 1);
42
+ TimePoint date1 = MillisecondsTimePoint.now();
43
+ TimePoint date2 = date1.plus(1);
46 44
assertTrue(MediaUtil.compareDatesAllowingNull(date1, date2) < 0);
47 45
}
48 46
49 47
@Test
50 48
public void testEqualsDatesAllNull() throws Exception {
51
- Date date1 = null;
52
- Date date2 = null;
49
+ TimePoint date1 = null;
50
+ TimePoint date2 = null;
53 51
assertTrue(MediaUtil.equalsDatesAllowingNull(date1, date2));
54 52
}
55 53
56 54
@Test
57 55
public void testEqualsDatesFirstNull() throws Exception {
58
- Date date1 = null;
59
- Date date2 = new Date();
56
+ TimePoint date1 = null;
57
+ TimePoint date2 = MillisecondsTimePoint.now();
60 58
assertFalse(MediaUtil.equalsDatesAllowingNull(date1, date2));
61 59
}
62 60
63 61
@Test
64 62
public void testEqualsDatesSecondNull() throws Exception {
65
- Date date1 = new Date();
66
- Date date2 = null;
63
+ TimePoint date1 = MillisecondsTimePoint.now();
64
+ TimePoint date2 = null;
67 65
assertFalse(MediaUtil.equalsDatesAllowingNull(date1, date2));
68 66
}
69 67
70 68
@Test
71 69
public void testEqualsDatesBothEqual() throws Exception {
72
- Date date1 = new Date();
73
- Date date2 = new Date(date1.getTime());
70
+ TimePoint date1 = MillisecondsTimePoint.now();
71
+ TimePoint date2 = date1.plus(0);
74 72
assertTrue(MediaUtil.equalsDatesAllowingNull(date1, date2));
75 73
}
76 74
77 75
@Test
78 76
public void testEqualsDatesNotEqual() throws Exception {
79
- Date date1 = new Date();
80
- Date date2 = new Date(date1.getTime() + 1);
77
+ TimePoint date1 = MillisecondsTimePoint.now();
78
+ TimePoint date2 = date1.plus(1);
81 79
assertFalse(MediaUtil.equalsDatesAllowingNull(date1, date2));
82 80
}
83 81
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/DurationTest.java
... ...
@@ -1,6 +1,8 @@
1 1
package com.sap.sailing.domain.common.test;
2 2
3
-import static org.junit.Assert.assertEquals;
3
+import static org.junit.Assert.*;
4
+
5
+import static org.hamcrest.Matchers.*;
4 6
5 7
import org.junit.Test;
6 8
... ...
@@ -98,4 +100,39 @@ public class DurationTest {
98 100
assertEquals(3600*25, oneDay.asSeconds(), 0.1);
99 101
assertEquals(3600*25*1000, oneDay.asMillis());
100 102
}
103
+
104
+ @Test
105
+ public void compare() {
106
+ assertThat(MillisecondsDurationImpl.ONE_MINUTE.compareTo(MillisecondsDurationImpl.ONE_HOUR), lessThan(0));
107
+ assertThat(MillisecondsDurationImpl.ONE_HOUR.compareTo(MillisecondsDurationImpl.ONE_MINUTE), greaterThan(0));
108
+ }
109
+
110
+ @Test
111
+ public void compareEquals() {
112
+ assertThat(MillisecondsDurationImpl.ONE_HOUR.compareTo(MillisecondsDurationImpl.ONE_HOUR), is(0));
113
+ }
114
+
115
+ @Test
116
+ public void testEqualForSame() {
117
+ assertThat(MillisecondsDurationImpl.ONE_HOUR, is(MillisecondsDurationImpl.ONE_HOUR));
118
+ }
119
+
120
+ @Test
121
+ public void testEqualForEuqal() {
122
+ Duration t1 = MillisecondsDurationImpl.ONE_HOUR;
123
+ Duration t2 = new MillisecondsDurationImpl(t1.asMillis());
124
+ assertThat(t1, is(t2));
125
+ assertThat(t2, is(t1));
126
+ }
127
+
128
+ @Test
129
+ public void testEqualForNull() {
130
+ assertFalse(MillisecondsDurationImpl.ONE_HOUR.equals(null));
131
+ }
132
+
133
+ @Test
134
+ public void testEqualForString() {
135
+ assertFalse(MillisecondsDurationImpl.ONE_HOUR.equals("s"));
136
+ }
137
+
101 138
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/TimePointTest.java
... ...
@@ -1,17 +1,56 @@
1 1
package com.sap.sailing.domain.common.test;
2 2
3
-import static org.junit.Assert.assertTrue;
4
-
5 3
import org.junit.Test;
6 4
7 5
import com.sap.sailing.domain.common.TimePoint;
8 6
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
9 7
8
+import static org.junit.Assert.*;
9
+
10
+import static org.hamcrest.Matchers.*;
11
+
10 12
public class TimePointTest {
11 13
@Test
12 14
public void compare() {
13 15
TimePoint one = new MillisecondsTimePoint(Long.MIN_VALUE);
14 16
TimePoint two = new MillisecondsTimePoint(Long.MAX_VALUE);
15 17
assertTrue(one.before(two));
18
+ assertTrue(two.after(one));
19
+ }
20
+
21
+ @Test
22
+ public void compareEquals() {
23
+ TimePoint one = new MillisecondsTimePoint(Long.MIN_VALUE);
24
+ TimePoint two = new MillisecondsTimePoint(Long.MIN_VALUE);
25
+ assertThat(one.compareTo(two), is(0));
26
+ assertFalse(one.before(two));
27
+ assertFalse(two.after(one));
28
+ }
29
+
30
+ @Test
31
+ public void testEqualForSame() {
32
+ TimePoint t = new MillisecondsTimePoint(Long.MIN_VALUE);
33
+ assertThat(t, is(t));
34
+ }
35
+
36
+ @Test
37
+ public void testEqualForEuqal() {
38
+ TimePoint t1 = new MillisecondsTimePoint(Long.MIN_VALUE);
39
+ TimePoint t2 = new MillisecondsTimePoint(Long.MIN_VALUE);
40
+ assertThat(t1, is(t2));
41
+ assertThat(t2, is(t1));
42
+ }
43
+
44
+ @Test
45
+ public void testEqualForNull() {
46
+ TimePoint t = new MillisecondsTimePoint(Long.MIN_VALUE);
47
+ assertFalse(t.equals(null));
16 48
}
49
+
50
+ @Test
51
+ public void testEqualForString() {
52
+ TimePoint t = new MillisecondsTimePoint(Long.MIN_VALUE);
53
+ assertFalse(t.equals("s"));
54
+ }
55
+
17 56
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CalculateImageSizeFromUrlTest.java
... ...
@@ -3,7 +3,6 @@ package com.sap.sailing.domain.test;
3 3
import static org.junit.Assert.assertEquals;
4 4
import static org.junit.Assert.assertTrue;
5 5
6
-import java.awt.Dimension;
7 6
import java.io.IOException;
8 7
import java.net.MalformedURLException;
9 8
import java.net.URL;
... ...
@@ -20,6 +19,8 @@ import org.junit.Test;
20 19
21 20
import com.sap.sailing.domain.base.Event;
22 21
import com.sap.sailing.domain.base.impl.EventImpl;
22
+import com.sap.sailing.domain.common.ImageSize;
23
+import com.sap.sailing.domain.common.impl.ImageSizeImpl;
23 24
24 25
public class CalculateImageSizeFromUrlTest {
25 26
... ...
@@ -27,12 +28,12 @@ public class CalculateImageSizeFromUrlTest {
27 28
public void calculateImageSizeFromUrl() throws IOException {
28 29
int width = 350;
29 30
int height = 150;
30
- Dimension size = calculate("http://placehold.it/" + width + "x" + height);
31
+ ImageSize size = calculate("http://placehold.it/" + width + "x" + height);
31 32
assertTrue(size.getWidth() == width);
32 33
assertTrue(size.getHeight() == height);
33 34
}
34 35
35
- public Dimension calculate(String urlS) throws IOException {
36
+ public ImageSize calculate(String urlS) throws IOException {
36 37
ImageInputStream in = null;
37 38
try {
38 39
URL url = new URL(urlS);
... ...
@@ -43,7 +44,7 @@ public class CalculateImageSizeFromUrlTest {
43 44
ImageReader reader = readers.next();
44 45
try {
45 46
reader.setInput(in);
46
- return new Dimension(reader.getWidth(0), reader.getHeight(0));
47
+ return new ImageSizeImpl(reader.getWidth(0), reader.getHeight(0));
47 48
} finally {
48 49
reader.dispose();
49 50
}
... ...
@@ -63,7 +64,7 @@ public class CalculateImageSizeFromUrlTest {
63 64
int height = Math.max(10, (int) (100. * Math.random()));
64 65
URL imageURL = new URL("http://placehold.it/" + width + "x" + height);
65 66
e.addImageURL(imageURL);
66
- Dimension expectedSize = new Dimension(width, height);
67
+ ImageSize expectedSize = new ImageSizeImpl(width, height);
67 68
assertEquals(expectedSize, e.getImageSize(imageURL));
68 69
}
69 70
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrentCollectionsPerformanceTest.java
... ...
@@ -0,0 +1,62 @@
1
+package com.sap.sailing.domain.test;
2
+
3
+import static org.junit.Assert.assertEquals;
4
+
5
+import java.util.ArrayList;
6
+import java.util.Collection;
7
+import java.util.Collections;
8
+import java.util.List;
9
+import java.util.concurrent.ConcurrentLinkedQueue;
10
+
11
+import org.junit.Test;
12
+
13
+import com.sap.sailing.util.impl.ConcurrentHashBag;
14
+import com.sap.sse.common.Util;
15
+
16
+/**
17
+ * Measures the performance of different concurrent collections types and shows their add and remove behavior for
18
+ * different collection sizes and different numbers of concurrently accessing threads. The collections used must be able
19
+ * to hold the same element multiple times, keeping count, not necessarily order.
20
+ *
21
+ * @author Axel Uhl (D043530)
22
+ *
23
+ */
24
+public class ConcurrentCollectionsPerformanceTest {
25
+ private static final int COUNT = 10000;
26
+
27
+ @Test
28
+ public void testConcurrentLinkedQueue() {
29
+ Collection<Object> c = new ConcurrentLinkedQueue<Object>();
30
+ System.out.println("ConcurrentLinkedQueue" + runWith(c));
31
+ }
32
+
33
+ @Test
34
+ public void testConcurrentHashMap() {
35
+ Collection<Object> c = new ConcurrentHashBag<Object>();
36
+ System.out.println("ConcurrentHashBag" + runWith(c));
37
+ }
38
+
39
+ private com.sap.sse.common.Util.Pair<Long, Long> runWith(Collection<Object> c) {
40
+ List<Object> l = new ArrayList<>(COUNT);
41
+ for (int i=0; i<COUNT; i++) {
42
+ l.add(new Object());
43
+ }
44
+ // add all objects a second time to ensure
45
+ for (Object o : new ArrayList<Object>(l)) {
46
+ l.add(o);
47
+ }
48
+ long startInsert = System.currentTimeMillis();
49
+ for (Object o : l) {
50
+ c.add(o);
51
+ }
52
+ long endInsert = System.currentTimeMillis();
53
+ assertEquals(l.size(), c.size());
54
+ Collections.shuffle(l);
55
+ long startRemove = System.currentTimeMillis();
56
+ for (Object o : l) {
57
+ c.remove(o);
58
+ }
59
+ long endRemove = System.currentTimeMillis();
60
+ return new Util.Pair<Long, Long>(endInsert-startInsert, endRemove-startRemove);
61
+ }
62
+}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java
... ...
@@ -24,6 +24,7 @@ import com.sap.sailing.domain.base.Sideline;
24 24
import com.sap.sailing.domain.base.Waypoint;
25 25
import com.sap.sailing.domain.base.impl.BoatClassImpl;
26 26
import com.sap.sailing.domain.base.impl.CompetitorImpl;
27
+import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
27 28
import com.sap.sailing.domain.base.impl.CourseImpl;
28 29
import com.sap.sailing.domain.base.impl.MarkImpl;
29 30
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
... ...
@@ -37,6 +38,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
37 38
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
38 39
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
39 40
import com.sap.sse.common.Util;
41
+import com.sap.sse.common.Util.Pair;
40 42
41 43
import difflib.PatchFailedException;
42 44
... ...
@@ -186,6 +188,53 @@ public class CourseTest {
186 188
course.update(courseToUpdate, DomainFactory.INSTANCE);
187 189
assertWaypointIndexes(course);
188 190
}
191
+
192
+ /**
193
+ * This test tries to replicate the behavior described in bug 2223. The course was
194
+ * "RC-Black Conical -> Orange -> White Gate -> Red -> Yellow -> Finish Pole-Cylinder" and the delta was
195
+ * "[[DeleteDelta, position: 1, lines: [Orange, White Gate]], [InsertDelta, position: 4, lines: [White Gate, Red]]]"
196
+ * which was computed from the new sequence of control points
197
+ * "[Control, RC-Black Conical, Control, Red, Control, White Gate, Control, Red, Control, Yellow, Control, Finish Pole-Cylinder]".
198
+ */
199
+ @Test
200
+ public void testWaypointDeleteWithSubsequentInsertInOnePatch() throws PatchFailedException {
201
+ List<Waypoint> waypoints = new ArrayList<Waypoint>();
202
+ final WaypointImpl rcBlackConical = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("RC"), new MarkImpl("Black Conical"), "RC-Black Conical"));
203
+ waypoints.add(rcBlackConical);
204
+ final WaypointImpl orange = new WaypointImpl(new MarkImpl("Orange"));
205
+ waypoints.add(orange);
206
+ final WaypointImpl whiteGate = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("White L"), new MarkImpl("White R"), "White Gate"));
207
+ waypoints.add(whiteGate);
208
+ final WaypointImpl red = new WaypointImpl(new MarkImpl("Red"));
209
+ waypoints.add(red);
210
+ final WaypointImpl yellow = new WaypointImpl(new MarkImpl("Yellow"));
211
+ waypoints.add(yellow);
212
+ final WaypointImpl finishPoleCylinder = new WaypointImpl(new ControlPointWithTwoMarksImpl(new MarkImpl("Finish Pole"), new MarkImpl("Cylinder"), "Finish Pole-Cylinder"));
213
+ waypoints.add(finishPoleCylinder);
214
+ Course course = new CourseImpl("Race 24", waypoints);
215
+ assertWaypointIndexes(course);
216
+ List<com.sap.sse.common.Util.Pair<ControlPoint, PassingInstruction>> courseToUpdate = new ArrayList<com.sap.sse.common.Util.Pair<ControlPoint, PassingInstruction>>();
217
+ courseToUpdate.add(new Pair<>(rcBlackConical.getControlPoint(), rcBlackConical.getPassingInstructions()));
218
+ courseToUpdate.add(new Pair<>(red.getControlPoint(), red.getPassingInstructions()));
219
+ courseToUpdate.add(new Pair<>(whiteGate.getControlPoint(), whiteGate.getPassingInstructions()));
220
+ courseToUpdate.add(new Pair<>(red.getControlPoint(), red.getPassingInstructions()));
221
+ courseToUpdate.add(new Pair<>(yellow.getControlPoint(), yellow.getPassingInstructions()));
222
+ courseToUpdate.add(new Pair<>(finishPoleCylinder.getControlPoint(), finishPoleCylinder.getPassingInstructions()));
223
+ course.update(courseToUpdate, DomainFactory.INSTANCE);
224
+ assertWaypointIndexes(course);
225
+ List<ControlPoint> newControlPoints = new ArrayList<>();
226
+ for (Waypoint newWp : course.getWaypoints()) {
227
+ newControlPoints.add(newWp.getControlPoint());
228
+ }
229
+ assertTrue(Util.equals(Arrays.asList(
230
+ rcBlackConical.getControlPoint(),
231
+ red.getControlPoint(),
232
+ whiteGate.getControlPoint(),
233
+ red.getControlPoint(),
234
+ yellow.getControlPoint(),
235
+ finishPoleCylinder.getControlPoint()),
236
+ newControlPoints));
237
+ }
189 238
190 239
@Test
191 240
public void testRemoveWaypointFromCourseWithThreeWaypoints() {
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LockTraceTest.java
... ...
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.test.measurements.MeasurementCase;
16 16
import com.sap.sailing.domain.test.measurements.MeasurementXMLFile;
17 17
import com.sap.sailing.util.impl.LockUtil;
18 18
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
19
+import com.sap.sse.common.Util;
19 20
20 21
public class LockTraceTest {
21 22
private static class LockingThread extends Thread {
... ...
@@ -47,18 +48,18 @@ public class LockTraceTest {
47 48
public void testReentrantReadLocking() {
48 49
NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("testReentrantReadLocking-Lock", /* fair */ true);
49 50
LockUtil.lockForRead(lock);
50
- assertTrue(lock.getReaders().contains(Thread.currentThread()));
51
- assertEquals(1, lock.getReaders().size());
51
+ assertTrue(Util.contains(lock.getReaders(), Thread.currentThread()));
52
+ assertEquals(1, Util.size(lock.getReaders()));
52 53
LockUtil.lockForRead(lock);
53
- assertTrue(lock.getReaders().contains(Thread.currentThread()));
54
- assertEquals(2, lock.getReaders().size());
54
+ assertTrue(Util.contains(lock.getReaders(), Thread.currentThread()));
55
+ assertEquals(2, Util.size(lock.getReaders()));
55 56
LockUtil.unlockAfterRead(lock);
56
- assertTrue(lock.getReaders().contains(Thread.currentThread()));
57
- assertEquals(1, lock.getReaders().size());
57
+ assertTrue(Util.contains(lock.getReaders(), Thread.currentThread()));
58
+ assertEquals(1, Util.size(lock.getReaders()));
58 59
LockUtil.unlockAfterRead(lock);
59
- assertFalse(lock.getReaders().contains(Thread.currentThread()));
60
+ assertFalse(Util.contains(lock.getReaders(), Thread.currentThread()));
60 61
assertEquals(0, lock.getReadHoldCount());
61
- assertEquals(0, lock.getReaders().size());
62
+ assertEquals(0, Util.size(lock.getReaders()));
62 63
}
63 64
64 65
@Test
... ...
@@ -67,15 +68,15 @@ public class LockTraceTest {
67 68
LockUtil.lockForWrite(lock);
68 69
assertTrue(lock.getWriter() == Thread.currentThread());
69 70
LockUtil.lockForRead(lock); // reentrant read while holding write
70
- assertTrue(lock.getReaders().contains(Thread.currentThread()));
71
+ assertTrue(Util.contains(lock.getReaders(), Thread.currentThread()));
71 72
LockUtil.lockForWrite(lock); // reentrant write
72 73
assertTrue(lock.getWriter() == Thread.currentThread());
73
- assertTrue(lock.getReaders().contains(Thread.currentThread()));
74
+ assertTrue(Util.contains(lock.getReaders(), Thread.currentThread()));
74 75
LockUtil.unlockAfterRead(lock);
75
- assertFalse(lock.getReaders().contains(Thread.currentThread()));
76
+ assertFalse(Util.contains(lock.getReaders(), Thread.currentThread()));
76 77
assertEquals(0, lock.getReadHoldCount());
77 78
assertTrue(lock.getWriter() == Thread.currentThread());
78
- assertFalse(lock.getReaders().contains(Thread.currentThread()));
79
+ assertFalse(Util.contains(lock.getReaders(), Thread.currentThread()));
79 80
assertEquals(0, lock.getReadHoldCount());
80 81
LockUtil.unlockAfterWrite(lock);
81 82
assertTrue(lock.getWriter() == Thread.currentThread());
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java
... ...
@@ -278,7 +278,7 @@ public class PolarSheetGenerationTest {
278 278
}
279 279
280 280
@Override
281
- public boolean isValid() {
281
+ public boolean isValidCached() {
282 282
return true;
283 283
}
284 284
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheDeadlockTest.java
... ...
@@ -19,6 +19,7 @@ import com.sap.sailing.util.SmartFutureCache;
19 19
import com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval;
20 20
import com.sap.sailing.util.impl.LockUtil;
21 21
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
22
+import com.sap.sse.common.Util;
22 23
23 24
/**
24 25
* When the thread reading a value from the cache holds a fair read lock, and another thread is trying to obtain the
... ...
@@ -88,15 +89,15 @@ public class SmartFutureCacheDeadlockTest {
88 89
@Test
89 90
public void testBasicLockingAndUnlockingWithScripts() throws InterruptedException {
90 91
logger.info("starting testBasicLockingAndUnlockingWithScripts");
91
- assertFalse(lock.getReaders().contains(readerThread));
92
+ assertFalse(Util.contains(lock.getReaders(), readerThread));
92 93
try {
93 94
reader.performAndWait(Command.LOCK_FOR_READ);
94
- assertTrue(lock.getReaders().contains(readerThread));
95
+ assertTrue(Util.contains(lock.getReaders(), readerThread));
95 96
writer.perform(Command.LOCK_FOR_WRITE);
96 97
assertNull(lock.getWriter());
97 98
} finally {
98 99
reader.performAndWait(Command.UNLOCK_AFTER_READ);
99
- assertFalse(lock.getReaders().contains(readerThread));
100
+ assertFalse(Util.contains(lock.getReaders(), readerThread));
100 101
writer.performAndWait(Command.UNLOCK_AFTER_WRITE);
101 102
}
102 103
}
... ...
@@ -195,7 +196,7 @@ public class SmartFutureCacheDeadlockTest {
195 196
writerThread.join();
196 197
assertFalse(reader.isRunning());
197 198
assertFalse(writer.isRunning());
198
- assertTrue(lock.getReaders().isEmpty());
199
+ assertTrue(Util.isEmpty(lock.getReaders()));
199 200
assertNull(lock.getWriter());
200 201
}
201 202
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java
... ...
@@ -26,6 +26,7 @@ import com.sap.sailing.domain.base.Boat;
26 26
import com.sap.sailing.domain.base.Timed;
27 27
import com.sap.sailing.domain.base.impl.BoatClassImpl;
28 28
import com.sap.sailing.domain.base.impl.BoatImpl;
29
+import com.sap.sailing.domain.common.AbstractBearing;
29 30
import com.sap.sailing.domain.common.Bearing;
30 31
import com.sap.sailing.domain.common.Distance;
31 32
import com.sap.sailing.domain.common.Position;
... ...
@@ -43,6 +44,7 @@ import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
43 44
import com.sap.sailing.domain.tracking.GPSFix;
44 45
import com.sap.sailing.domain.tracking.GPSFixMoving;
45 46
import com.sap.sailing.domain.tracking.GPSFixTrack;
47
+import com.sap.sailing.domain.tracking.impl.CompactGPSFixMovingImpl;
46 48
import com.sap.sailing.domain.tracking.impl.DistanceCache;
47 49
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
48 50
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
... ...
@@ -852,7 +854,7 @@ public class TrackTest {
852 854
true), null), /* millisecondsOverWhichToAverage */5000, /* no smoothening */null);
853 855
TimePoint now1 = MillisecondsTimePoint.now();
854 856
TimePoint now2 = addMillisToTimepoint(now1, 1000); // 1s
855
- DegreeBearingImpl bearing = new DegreeBearingImpl(90);
857
+ AbstractBearing bearing = new DegreeBearingImpl(90);
856 858
Position position1 = new DegreePosition(1, 2);
857 859
Position position2 = position1.translateGreatCircle(bearing, new MeterDistance(1));
858 860
GPSFixMovingImpl myGpsFix1 = new GPSFixMovingImpl(position1, now1, new MeterPerSecondSpeedWithDegreeBearingImpl(1, bearing));
... ...
@@ -873,7 +875,7 @@ public class TrackTest {
873 875
true), null), /* millisecondsOverWhichToAverage */5000, /* no smoothening */null);
874 876
TimePoint now1 = MillisecondsTimePoint.now();
875 877
TimePoint now2 = addMillisToTimepoint(now1, 1000); // 1s
876
- DegreeBearingImpl bearing = new DegreeBearingImpl(90);
878
+ AbstractBearing bearing = new DegreeBearingImpl(90);
877 879
Position position1 = new DegreePosition(1, 2);
878 880
Position position2 = position1.translateGreatCircle(bearing, new MeterDistance(1));
879 881
GPSFixMovingImpl myGpsFix1 = new GPSFixMovingImpl(position1, now1, new MeterPerSecondSpeedWithDegreeBearingImpl(10, bearing));
... ...
@@ -894,7 +896,7 @@ public class TrackTest {
894 896
true), null), /* millisecondsOverWhichToAverage */5000, /* no smoothening */null);
895 897
TimePoint now1 = MillisecondsTimePoint.now();
896 898
TimePoint now2 = addMillisToTimepoint(now1, 1000); // 1s
897
- DegreeBearingImpl bearing = new DegreeBearingImpl(90);
899
+ AbstractBearing bearing = new DegreeBearingImpl(90);
898 900
Position position1 = new DegreePosition(1, 2);
899 901
Position position2 = position1.translateGreatCircle(bearing, new MeterDistance(1));
900 902
GPSFixMovingImpl myGpsFix1 = new GPSFixMovingImpl(position1, now1, new MeterPerSecondSpeedWithDegreeBearingImpl(0.1, bearing));
... ...
@@ -1053,4 +1055,33 @@ public class TrackTest {
1053 1055
assertEquals(3, track.getAverageIntervalBetweenFixes().asMillis());
1054 1056
assertEquals(3, track.getAverageIntervalBetweenRawFixes().asMillis());
1055 1057
}
1058
+
1059
+ @Test
1060
+ public void testEstimatedSpeedCaching() {
1061
+ GPSFixMoving originalFix = new GPSFixMovingImpl(new DegreePosition(12, 34), MillisecondsTimePoint.now(),
1062
+ new KnotSpeedWithBearingImpl(9, new DegreeBearingImpl(123)));
1063
+ GPSFixMoving fix = new CompactGPSFixMovingImpl(originalFix);
1064
+ assertFalse(fix.isEstimatedSpeedCached());
1065
+ final KnotSpeedWithBearingImpl estimatedSpeed = new KnotSpeedWithBearingImpl(9.1, new DegreeBearingImpl(124));
1066
+ fix.cacheEstimatedSpeed(estimatedSpeed);
1067
+ assertTrue(fix.isEstimatedSpeedCached());
1068
+ SpeedWithBearing cachedEstimatedSpeed = fix.getCachedEstimatedSpeed();
1069
+ assertEquals(estimatedSpeed, cachedEstimatedSpeed);
1070
+ fix.invalidateEstimatedSpeedCache();
1071
+ assertFalse(fix.isEstimatedSpeedCached());
1072
+ }
1073
+
1074
+ @Test
1075
+ public void testEstimatedSpeedCachingOnTrack() {
1076
+ GPSFixMoving compactFix3 = track.getFirstFixAtOrAfter(gpsFix3.getTimePoint());
1077
+ assertFalse(compactFix3.isEstimatedSpeedCached());
1078
+ SpeedWithBearing fix3EstimatedSpeed = track.getEstimatedSpeed(gpsFix3.getTimePoint());
1079
+ assertTrue(compactFix3.isEstimatedSpeedCached());
1080
+ assertEquals(fix3EstimatedSpeed, compactFix3.getCachedEstimatedSpeed());
1081
+ assertEquals(fix3EstimatedSpeed, track.getEstimatedSpeed(gpsFix3.getTimePoint())); // fetch again from the cache
1082
+ // assuming that all test fixes are within a few milliseconds and the averaging interval is much larger than that,
1083
+ // adding a single fix in the middle should invalidate the cache
1084
+ track.add(new GPSFixMovingImpl(gpsFix3.getPosition(), gpsFix3.getTimePoint().plus(1), gpsFix3.getSpeed()));
1085
+ assertFalse(compactFix3.isEstimatedSpeedCached());
1086
+ }
1056 1087
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java
... ...
@@ -1,10 +1,10 @@
1 1
package com.sap.sailing.domain.base;
2 2
3
-import java.awt.Dimension;
4 3
import java.net.URL;
5 4
import java.util.UUID;
6 5
import java.util.concurrent.ExecutionException;
7 6
7
+import com.sap.sailing.domain.common.ImageSize;
8 8
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
9 9
10 10
/**
... ...
@@ -59,6 +59,6 @@ public interface Event extends EventBase {
59 59
* Note that exceptions may result should the image be unavailable. If the image URL leads to a document that is not an image,
60 60
* <code>null</code> will result.
61 61
*/
62
- Dimension getImageSize(URL imageURL) throws InterruptedException, ExecutionException;
62
+ ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException;
63 63
64 64
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java
... ...
@@ -47,6 +47,7 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
47 47
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive;
48 48
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFiveIgnoringRaceCount;
49 49
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix;
50
+import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSixIgnoringRaceCount;
50 51
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
51 52
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
52 53
import com.sap.sailing.domain.tracking.GPSFix;
... ...
@@ -108,6 +109,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
108 109
return new HighPointWinnerGetsFiveIgnoringRaceCount();
109 110
case HIGH_POINT_WINNER_GETS_SIX:
110 111
return new HighPointWinnerGetsSix();
112
+ case HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT:
113
+ return new HighPointWinnerGetsSixIgnoringRaceCount();
111 114
case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT:
112 115
return new HighPointFirstGets10Or8AndLastBreaksTie();
113 116
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java
... ...
@@ -1,6 +1,5 @@
1 1
package com.sap.sailing.domain.base.impl;
2 2
3
-import java.awt.Dimension;
4 3
import java.io.IOException;
5 4
import java.io.ObjectInputStream;
6 5
import java.net.URL;
... ...
@@ -25,7 +24,9 @@ import javax.imageio.stream.ImageInputStream;
25 24
import com.sap.sailing.domain.base.Event;
26 25
import com.sap.sailing.domain.base.Regatta;
27 26
import com.sap.sailing.domain.base.Venue;
27
+import com.sap.sailing.domain.common.ImageSize;
28 28
import com.sap.sailing.domain.common.TimePoint;
29
+import com.sap.sailing.domain.common.impl.ImageSizeImpl;
29 30
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
30 31
import com.sap.sse.common.Util;
31 32
... ...
@@ -36,7 +37,7 @@ public class EventImpl extends EventBaseImpl implements Event {
36 37
37 38
private ConcurrentLinkedQueue<LeaderboardGroup> leaderboardGroups;
38 39
39
- private transient ConcurrentHashMap<URL, Future<Dimension>> imageSizeFetchers;
40
+ private transient ConcurrentHashMap<URL, Future<ImageSize>> imageSizeFetchers;
40 41
private transient ExecutorService executor;
41 42
42 43
public EventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) {
... ...
@@ -103,13 +104,13 @@ public class EventImpl extends EventBaseImpl implements Event {
103 104
return leaderboardGroups.remove(leaderboardGroup);
104 105
}
105 106
106
- private Future<Dimension> getOrCreateImageSizeCalculator(final URL imageURL) {
107
- Future<Dimension> imageSizeFetcher = imageSizeFetchers.get(imageURL);
107
+ private Future<ImageSize> getOrCreateImageSizeCalculator(final URL imageURL) {
108
+ Future<ImageSize> imageSizeFetcher = imageSizeFetchers.get(imageURL);
108 109
if (imageSizeFetcher == null) {
109
- imageSizeFetcher = executor.submit(new Callable<Dimension>() {
110
+ imageSizeFetcher = executor.submit(new Callable<ImageSize>() {
110 111
@Override
111
- public Dimension call() throws IOException {
112
- Dimension result = null;
112
+ public ImageSize call() throws IOException {
113
+ ImageSize result = null;
113 114
ImageInputStream in = null;
114 115
try {
115 116
URLConnection conn = imageURL.openConnection();
... ...
@@ -119,7 +120,7 @@ public class EventImpl extends EventBaseImpl implements Event {
119 120
ImageReader reader = readers.next();
120 121
try {
121 122
reader.setInput(in);
122
- result = new Dimension(reader.getWidth(0), reader.getHeight(0));
123
+ result = new ImageSizeImpl(reader.getWidth(0), reader.getHeight(0));
123 124
} finally {
124 125
reader.dispose();
125 126
}
... ...
@@ -138,8 +139,8 @@ public class EventImpl extends EventBaseImpl implements Event {
138 139
}
139 140
140 141
@Override
141
- public Dimension getImageSize(URL imageURL) throws InterruptedException, ExecutionException {
142
- Future<Dimension> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL);
142
+ public ImageSize getImageSize(URL imageURL) throws InterruptedException, ExecutionException {
143
+ Future<ImageSize> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL);
143 144
return imageSizeCalculator.get();
144 145
}
145 146
... ...
@@ -171,8 +172,10 @@ public class EventImpl extends EventBaseImpl implements Event {
171 172
@Override
172 173
public void setImageURLs(Iterable<URL> imageURLs) {
173 174
super.setImageURLs(imageURLs);
174
- for (URL imageURL : imageURLs) {
175
- refreshImageSizeFetcher(imageURL);
175
+ if (imageURLs != null) {
176
+ for (URL imageURL : imageURLs) {
177
+ refreshImageSizeFetcher(imageURL);
178
+ }
176 179
}
177 180
}
178 181
... ...
@@ -191,8 +194,10 @@ public class EventImpl extends EventBaseImpl implements Event {
191 194
@Override
192 195
public void setSponsorImageURLs(Iterable<URL> sponsorImageURLs) {
193 196
super.setSponsorImageURLs(sponsorImageURLs);
194
- for (URL imageURL : sponsorImageURLs) {
195
- refreshImageSizeFetcher(imageURL);
197
+ if (sponsorImageURLs != null) {
198
+ for (URL imageURL : sponsorImageURLs) {
199
+ refreshImageSizeFetcher(imageURL);
200
+ }
196 201
}
197 202
}
198 203
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java
... ...
@@ -1659,7 +1659,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
1659 1659
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1660 1660
LeaderboardDTOCalculationReuseCache cache) throws NoWindException {
1661 1661
LegEntryDTO result;
1662
- if (trackedLeg == null || trackedLeg.getTime(timePoint) == null) {
1662
+ final Duration time = trackedLeg.getTime(timePoint);
1663
+ if (trackedLeg == null || time == null) {
1663 1664
result = null;
1664 1665
} else {
1665 1666
result = new LegEntryDTO();
... ...
@@ -1681,7 +1682,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
1681 1682
Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
1682 1683
result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters();
1683 1684
result.estimatedTimeToNextWaypointInSeconds = trackedLeg.getEstimatedTimeToNextMarkInSeconds(timePoint, WindPositionMode.EXACT, cache);
1684
- result.timeInMilliseconds = trackedLeg.getTime(timePoint).asMillis();
1685
+ result.timeInMilliseconds = time.asMillis();
1685 1686
result.finished = trackedLeg.hasFinishedLeg(timePoint);
1686 1687
result.gapToLeaderInSeconds = trackedLeg.getGapToLeaderInSeconds(timePoint,
1687 1688
legRanksCache.get(trackedLeg.getLeg()).entrySet().iterator().next().getKey(), WindPositionMode.LEG_MIDDLE);
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointWinnerGetsSixIgnoringRaceCount.java
... ...
@@ -0,0 +1,20 @@
1
+package com.sap.sailing.domain.leaderboard.impl;
2
+
3
+import com.sap.sailing.domain.common.ScoringSchemeType;
4
+
5
+public class HighPointWinnerGetsSixIgnoringRaceCount extends HighPointWinnerGetsSix {
6
+ private static final long serialVersionUID = -6974180846946052247L;
7
+
8
+ /**
9
+ * Ignores the number of races scored, making the simple total points the first ranking criterion.
10
+ */
11
+ @Override
12
+ public int compareByNumberOfRacesScored(int competitor1NumberOfRacesScored, int competitor2NumberOfRacesScored) {
13
+ return 0;
14
+ }
15
+
16
+ @Override
17
+ public ScoringSchemeType getType() {
18
+ return ScoringSchemeType.HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT;
19
+ }
20
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFix.java
... ...
@@ -3,6 +3,6 @@ package com.sap.sailing.domain.tracking;
3 3
import com.sap.sailing.domain.base.Timed;
4 4
import com.sap.sailing.domain.common.SpeedWithBearing;
5 5
6
-public interface GPSFix extends Positioned, Timed, WithValidityCache {
6
+public interface GPSFix extends Positioned, Timed, WithValidityCache, WithEstimatedSpeedCache {
7 7
SpeedWithBearing getSpeedAndBearingRequiredToReach(GPSFix to);
8 8
}
... ...
\ No newline at end of file
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/MarkPassingListener.java
... ...
@@ -1,6 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-
4
-public interface MarkPassingListener {
5
- void legCompleted(MarkPassing markPassing);
6
-}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithEstimatedSpeedCache.java
... ...
@@ -0,0 +1,17 @@
1
+package com.sap.sailing.domain.tracking;
2
+
3
+import com.sap.sailing.domain.common.SpeedWithBearing;
4
+
5
+public interface WithEstimatedSpeedCache {
6
+ boolean isEstimatedSpeedCached();
7
+
8
+ /**
9
+ * Returns a valid result if {@link #isEstimatedSpeedCached()} returns <code>true</code>
10
+ */
11
+ SpeedWithBearing getCachedEstimatedSpeed();
12
+
13
+ void invalidateEstimatedSpeedCache();
14
+
15
+ void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed);
16
+
17
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WithValidityCache.java
... ...
@@ -3,7 +3,10 @@ package com.sap.sailing.domain.tracking;
3 3
public interface WithValidityCache {
4 4
boolean isValidityCached();
5 5
6
- boolean isValid();
6
+ /**
7
+ * Returns a valid result if {@link #isValidCached()} returns <code>true</code>
8
+ */
9
+ boolean isValidCached();
7 10
8 11
void invalidateCache();
9 12
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractGPSFixImpl.java
... ...
@@ -48,14 +48,21 @@ public abstract class AbstractGPSFixImpl implements GPSFix {
48 48
Speed speed = distance.inTime(to.getTimePoint().asMillis()-getTimePoint().asMillis());
49 49
return new KnotSpeedWithBearingImpl(speed.getKnots(), bearing);
50 50
}
51
-
51
+
52
+ /**
53
+ * Subclasses overriding this method also need to override {@link #isValid} with a useful implementation.
54
+ */
52 55
@Override
53 56
public boolean isValidityCached() {
54 57
return false;
55 58
}
56 59
60
+ /**
61
+ * Only evaluated if {@link #isValidityCached()} returns <code>true</code>. Therefore, subclassess that
62
+ * override {@link #isValidityCached()} also need to override this method accordingly.
63
+ */
57 64
@Override
58
- public boolean isValid() {
65
+ public boolean isValidCached() {
59 66
return false;
60 67
}
61 68
... ...
@@ -66,4 +73,29 @@ public abstract class AbstractGPSFixImpl implements GPSFix {
66 73
@Override
67 74
public void cacheValidity(boolean isValid) {
68 75
}
76
+
77
+ /**
78
+ * Subclasses overriding this method also need to override {@link #getCachedEstimatedSpeed()} with a useful implementation.
79
+ */
80
+ @Override
81
+ public boolean isEstimatedSpeedCached() {
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * Only evaluated if {@link #isEstimatedSpeedCached()} returns <code>true</code>. Therefore, subclassess that
87
+ * override {@link #isEstimatedSpeedCached()} also need to override this method accordingly.
88
+ */
89
+ @Override
90
+ public SpeedWithBearing getCachedEstimatedSpeed() {
91
+ return null;
92
+ }
93
+
94
+ @Override
95
+ public void invalidateEstimatedSpeedCache() {
96
+ }
97
+
98
+ @Override
99
+ public void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed) {
100
+ }
69 101
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixImpl.java
... ...
@@ -1,8 +1,12 @@
1 1
package com.sap.sailing.domain.tracking.impl;
2 2
3
+import com.sap.sailing.domain.common.AbstractBearing;
3 4
import com.sap.sailing.domain.common.AbstractPosition;
5
+import com.sap.sailing.domain.common.Bearing;
4 6
import com.sap.sailing.domain.common.Position;
7
+import com.sap.sailing.domain.common.SpeedWithBearing;
5 8
import com.sap.sailing.domain.common.TimePoint;
9
+import com.sap.sailing.domain.common.impl.AbstractSpeedWithAbstractBearingImpl;
6 10
import com.sap.sailing.domain.common.impl.AbstractTimePoint;
7 11
import com.sap.sailing.domain.tracking.GPSFix;
8 12
... ...
@@ -19,6 +23,23 @@ import com.sap.sailing.domain.tracking.GPSFix;
19 23
*/
20 24
public class CompactGPSFixImpl extends AbstractGPSFixImpl {
21 25
private static final long serialVersionUID = 8167588584536992501L;
26
+
27
+ /**
28
+ * Bit mask for {@link #whatIsCached}, telling whether validity is currently cached
29
+ */
30
+ private static final byte IS_VALIDITY_CACHED = 1<<0;
31
+
32
+ /**
33
+ * Bit mask for {@link #whatIsCached}, telling whether the estimated speed is currently cached
34
+ */
35
+ private static final byte IS_ESTIMATED_SPEED_CACHED = 1<<1;
36
+
37
+ /**
38
+ * Bit mask for {@link #whatIsCached}, telling the validity of the fix; only relevant if
39
+ * <code>{@link #whatIsCached}&amp;{@link #IS_VALIDITY_CACHED} != 0</code>
40
+ */
41
+ private static final byte VALIDITY = 1<<2;
42
+
22 43
private final double latDeg;
23 44
private final double lngDeg;
24 45
private final long timePointAsMillis;
... ...
@@ -28,7 +49,19 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl {
28 49
* needs to be invalidated as soon as fixes are added to the containing track which may have an impact
29 50
* on this fix's validity. -1 means "no value"; 0 means invalid, 1 means valid.
30 51
*/
31
- private byte validityCache = -1;
52
+ private byte whatIsCached = 0;
53
+
54
+ /**
55
+ * When <code>{@link #whatIsCached}&amp;{@link #IS_ESTIMATED_SPEED_CACHED} != 0</code>, this field tells the estimated speed's
56
+ * true "bearing" (true course over ground) in degrees.
57
+ */
58
+ private double cachedEstimatedSpeedBearingInDegrees;
59
+
60
+ /**
61
+ * When <code>{@link #whatIsCached}&amp;{@link #IS_ESTIMATED_SPEED_CACHED} != 0</code>, this field tells the estimated speed
62
+ * in knots.
63
+ */
64
+ private double cachedEstimatedSpeedInKnots;
32 65
33 66
private class CompactPosition extends AbstractPosition {
34 67
private static final long serialVersionUID = 5621506820766614178L;
... ...
@@ -53,6 +86,29 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl {
53 86
}
54 87
}
55 88
89
+ private class CompactEstimatedSpeedBearing extends AbstractBearing {
90
+ private static final long serialVersionUID = 8549231429037883121L;
91
+
92
+ @Override
93
+ public double getDegrees() {
94
+ return cachedEstimatedSpeedBearingInDegrees;
95
+ }
96
+ }
97
+
98
+ private class CompactEstimatedSpeed extends AbstractSpeedWithAbstractBearingImpl {
99
+ private static final long serialVersionUID = -5871855443391817248L;
100
+
101
+ @Override
102
+ public Bearing getBearing() {
103
+ return new CompactEstimatedSpeedBearing();
104
+ }
105
+
106
+ @Override
107
+ public double getKnots() {
108
+ return cachedEstimatedSpeedInKnots;
109
+ }
110
+ }
111
+
56 112
public CompactGPSFixImpl(Position position, TimePoint timePoint) {
57 113
latDeg = position.getLatDeg();
58 114
lngDeg = position.getLngDeg();
... ...
@@ -80,22 +136,52 @@ public class CompactGPSFixImpl extends AbstractGPSFixImpl {
80 136
81 137
@Override
82 138
public boolean isValidityCached() {
83
- return validityCache != -1;
139
+ return (whatIsCached & IS_VALIDITY_CACHED) != 0;
84 140
}
85 141
86 142
@Override
87
- public boolean isValid() {
88
- return validityCache == 1;
143
+ public boolean isValidCached() {
144
+ assert isValidityCached();
145
+ return (whatIsCached & VALIDITY) != 0;
89 146
}
90 147
91 148
@Override
92 149
public void invalidateCache() {
93
- validityCache = -1;
150
+ whatIsCached &= ~IS_VALIDITY_CACHED;
94 151
}
95 152
96 153
@Override
97 154
public void cacheValidity(boolean isValid) {
98
- validityCache = (byte) (isValid ? 1 : 0);
155
+ if (isValid) {
156
+ whatIsCached |= IS_VALIDITY_CACHED | VALIDITY;
157
+ } else {
158
+ whatIsCached |= IS_VALIDITY_CACHED;
159
+ whatIsCached &= ~VALIDITY;
160
+ }
161
+ }
162
+
163
+ @Override
164
+ public boolean isEstimatedSpeedCached() {
165
+ return (whatIsCached & IS_ESTIMATED_SPEED_CACHED) != 0;
99 166
}
167
+
168
+ @Override
169
+ public SpeedWithBearing getCachedEstimatedSpeed() {
170
+ assert isEstimatedSpeedCached();
171
+ return new CompactEstimatedSpeed();
172
+ }
173
+
174
+ @Override
175
+ public void invalidateEstimatedSpeedCache() {
176
+ whatIsCached &= ~IS_ESTIMATED_SPEED_CACHED;
177
+ }
178
+
179
+ @Override
180
+ public void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed) {
181
+ cachedEstimatedSpeedBearingInDegrees = estimatedSpeed.getBearing().getDegrees();
182
+ cachedEstimatedSpeedInKnots = estimatedSpeed.getKnots();
183
+ whatIsCached |= IS_ESTIMATED_SPEED_CACHED;
184
+ }
185
+
100 186
101 187
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java
... ...
@@ -123,7 +123,7 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends GPSFixTrackImpl<Item
123 123
assertReadLock();
124 124
final boolean isValid;
125 125
if (e.isValidityCached()) {
126
- isValid = e.isValid();
126
+ isValid = e.isValidCached();
127 127
} else {
128 128
boolean fixHasValidSogAndCog = (e.getSpeed().getMetersPerSecond() != 0.0 || e.getSpeed().getBearing().getDegrees() != 0.0) &&
129 129
(maxSpeedForSmoothing == null || e.getSpeed().compareTo(maxSpeedForSmoothing) <= 0);
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java
... ...
@@ -166,6 +166,9 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
166 166
167 167
private transient MaxSpeedCache<ItemType, FixType> maxSpeedCache;
168 168
169
+ private int estimatedSpeedCacheHits;
170
+ private int estimatedSpeedCacheMisses;
171
+
169 172
public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) {
170 173
this(trackedItem, millisecondsOverWhichToAverage, DEFAULT_MAX_SPEED_FOR_SMOOTHING);
171 174
}
... ...
@@ -230,7 +233,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
230 233
}
231 234
232 235
@Override
233
- public boolean isValid() {
236
+ public boolean isValidCached() {
234 237
return false;
235 238
}
236 239
... ...
@@ -241,6 +244,24 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
241 244
@Override
242 245
public void cacheValidity(boolean isValid) {
243 246
}
247
+
248
+ @Override
249
+ public boolean isEstimatedSpeedCached() {
250
+ return false;
251
+ }
252
+
253
+ @Override
254
+ public SpeedWithBearing getCachedEstimatedSpeed() {
255
+ return null;
256
+ }
257
+
258
+ @Override
259
+ public void invalidateEstimatedSpeedCache() {
260
+ }
261
+
262
+ @Override
263
+ public void cacheEstimatedSpeed(SpeedWithBearing estimatedSpeed) {
264
+ }
244 265
}
245 266
246 267
... ...
@@ -577,12 +598,29 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
577 598
@Override
578 599
public SpeedWithBearing getEstimatedSpeed(TimePoint at) {
579 600
lockForRead();
601
+ FixType ceil = getInternalFixes().ceiling(createDummyGPSFix(at));
580 602
try {
581
- SpeedWithBearingWithConfidence<TimePoint> estimatedSpeed = getEstimatedSpeed(at, getInternalFixes(),
603
+ final SpeedWithBearing result;
604
+ if (ceil != null && ceil.getTimePoint().equals(at) && ceil.isEstimatedSpeedCached()) {
605
+ estimatedSpeedCacheHits++;
606
+ result = ceil.getCachedEstimatedSpeed();
607
+ } else {
608
+ estimatedSpeedCacheMisses++;
609
+ SpeedWithBearingWithConfidence<TimePoint> estimatedSpeed = getEstimatedSpeed(at, getInternalFixes(),
582 610
ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher(
583 611
// use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero
584 612
getMillisecondsOverWhichToAverageSpeed()/2, /* minimumConfidence */ 0.00000001)); // half confidence if half averaging interval apart
585
- return estimatedSpeed == null ? null : estimatedSpeed.getObject();
613
+ result = estimatedSpeed == null ? null : estimatedSpeed.getObject();
614
+ if (estimatedSpeed != null) {
615
+ if (ceil != null && ceil.getTimePoint().equals(at)) {
616
+ ceil.cacheEstimatedSpeed(result);
617
+ }
618
+ }
619
+ }
620
+ if (logger.isLoggable(Level.FINE) && (estimatedSpeedCacheHits + estimatedSpeedCacheMisses) % 1000 == 0) {
621
+ logger.fine("estimated speed cache hits/misses: "+estimatedSpeedCacheHits+"/"+estimatedSpeedCacheMisses);
622
+ }
623
+ return result;
586 624
} finally {
587 625
unlockAfterRead();
588 626
}
... ...
@@ -769,6 +807,9 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
769 807
*
770 808
* But even for a track with {@link GPSFixMoving} fixes this is a good algorithm because in case the speed changes
771 809
* significantly between fixes, it is important to know the next fix to understand and consider the trend.
810
+ *
811
+ * @see #getMillisecondsOverWhichToAverage()
812
+ * @see #getMillisecondsOverWhichToAverageSpeed()
772 813
*/
773 814
protected List<FixType> getFixesRelevantForSpeedEstimation(TimePoint at, NavigableSet<FixType> fixesToUseForSpeedEstimation) {
774 815
lockForRead();
... ...
@@ -858,7 +899,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
858 899
}
859 900
860 901
/**
861
- * When redefining this method, make sure to redefine {@link #invalidateValidityAndDistanceCaches(GPSFix)}
902
+ * When redefining this method, make sure to redefine {@link #invalidateValidityAndEstimatedSpeedAndDistanceCaches(GPSFix)}
862 903
* accordingly. This implementation checks the immediate previous and next fix for <code>e</code>. Therefore, when
863 904
* adding a fix, only immediately adjacent fix's validity caches need to be invalidated.
864 905
* <p>
... ...
@@ -875,7 +916,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
875 916
isValid = true;
876 917
} else {
877 918
if (e.isValidityCached()) {
878
- isValid = e.isValid();
919
+ isValid = e.isValidCached();
879 920
} else {
880 921
FixType previous = rawFixes.lower(e);
881 922
final boolean atLeastOnePreviousFixInRange = previous != null && e.getTimePoint().asMillis() - previous.getTimePoint().asMillis() <= getMillisecondsOverWhichToAverageSpeed();
... ...
@@ -918,7 +959,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
918 959
* of the <code>gpsFix</code> "upwards." However, if the adjacent earlier fixes have changed their validity by the addition
919 960
* of <code>gpsFix</code>, the distance cache must be invalidated starting with the first fix whose validity changed.
920 961
*/
921
- protected void invalidateValidityAndDistanceCaches(FixType gpsFix) {
962
+ protected void invalidateValidityAndEstimatedSpeedAndDistanceCaches(FixType gpsFix) {
922 963
assertWriteLock();
923 964
TimePoint distanceCacheInvalidationStart = gpsFix.getTimePoint();
924 965
// see also bug 968: cache entries for intervals ending after the last fix need to be removed because they are
... ...
@@ -930,6 +971,10 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
930 971
distanceCacheInvalidationStart = last.getTimePoint().plus(1); // add one millisecond to invalidate *after* the last fix only
931 972
}
932 973
gpsFix.invalidateCache();
974
+ for (FixType fixOnWhichToInvalidateEstimatedSpeed : getFixesRelevantForSpeedEstimation(gpsFix.getTimePoint(),
975
+ getInternalRawFixes())) {
976
+ fixOnWhichToInvalidateEstimatedSpeed.invalidateEstimatedSpeedCache();
977
+ }
933 978
Iterable<FixType> lowers = getEarlierFixesWhoseValidityMayBeAffected(gpsFix);
934 979
for (FixType lower : lowers) {
935 980
boolean lowerWasValid = isValid(getRawFixes(), lower);
... ...
@@ -1014,7 +1059,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
1014 1059
try {
1015 1060
firstFixInTrack = getRawFixes().isEmpty();
1016 1061
result = addWithoutLocking(fix);
1017
- invalidateValidityAndDistanceCaches(fix);
1062
+ invalidateValidityAndEstimatedSpeedAndDistanceCaches(fix);
1018 1063
} finally {
1019 1064
unlockAfterWrite();
1020 1065
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java
... ...
@@ -257,7 +257,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
257 257
@Override
258 258
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
259 259
clearCaches(); // necessary because the competitor tracks ordered by rank may change for legs adjacent to the waypoint added
260
- }
260
+ } // and because the leg's from/to waypoints may have changed
261 261
262 262
@Override
263 263
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
... ...
@@ -220,7 +220,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
220 220
* them.
221 221
*/
222 222
private transient SmartFutureCache<Competitor, com.sap.sse.common.Util.Triple<TimePoint, TimePoint, List<Maneuver>>, EmptyUpdateInterval> maneuverCache;
223
-
223
+
224 224
private transient Map<TimePoint, Future<Wind>> directionFromStartToNextMarkCache;
225 225
226 226
private final ConcurrentHashMap<Mark, GPSFixTrack<Mark, GPSFix>> markTracks;
... ...
@@ -506,7 +506,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
506 506
}
507 507
}, /* nameForLocks */"Maneuver cache for race " + getRace().getName());
508 508
}
509
-
509
+
510 510
/**
511 511
* Precondition: race has already been set, e.g., in constructor before this method is called
512 512
*/
java/com.sap.sailing.domain/src/com/sap/sailing/util/SmartFutureCache.java
... ...
@@ -507,7 +507,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
507 507
* from the cache straight away (<code>waitForLatest==false</code>) or, if a re-calculation for the <code>key</code> is still
508 508
* ongoing, the result of that ongoing re-calculation is returned.
509 509
*/
510
- public V get(K key, boolean waitForLatest) {
510
+ public V get(final K key, boolean waitForLatest) {
511 511
V value = null;
512 512
if (waitForLatest) {
513 513
FutureTaskWithCancelBlocking future;
... ...
@@ -545,11 +545,12 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
545 545
}
546 546
} // else no calculation currently going on; value has been fetched from latest cache entry
547 547
} else {
548
- LockUtil.lockForRead(getOrCreateLockForKey(key));
548
+ final NamedReentrantReadWriteLock lock = getOrCreateLockForKey(key);
549
+ LockUtil.lockForRead(lock);
549 550
try {
550 551
value = cache.get(key);
551 552
} finally {
552
- LockUtil.unlockAfterRead(getOrCreateLockForKey(key));
553
+ LockUtil.unlockAfterRead(lock);
553 554
}
554 555
}
555 556
return value;
... ...
@@ -576,4 +577,11 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
576 577
return smartFutureCacheTaskReuseCounter;
577 578
}
578 579
580
+ /**
581
+ * Removes the key from the cache. If any updates are still running, they may again insert the key into hte cache.
582
+ */
583
+ public void remove(K key) {
584
+ cache(key, null);
585
+ }
586
+
579 587
}
java/com.sap.sailing.gwt.ui.test/.classpath
... ...
@@ -1,10 +1,10 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<classpath>
3
- <classpathentry kind="src" path="src"/>
4
- <classpathentry kind="src" path="resources"/>
5
- <classpathentry exported="true" kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
6
- <classpathentry exported="true" kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
7
- <classpathentry exported="true" kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
8
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
9
- <classpathentry kind="output" path="bin"/>
10
-</classpath>
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<classpath>
3
+ <classpathentry kind="src" path="src"/>
4
+ <classpathentry kind="src" path="resources"/>
5
+ <classpathentry exported="true" kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
6
+ <classpathentry exported="true" kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
7
+ <classpathentry exported="true" kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
8
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
9
+ <classpathentry kind="output" path="bin"/>
10
+</classpath>
java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java
... ...
@@ -209,7 +209,7 @@ public class PolarSheetGenerationServiceTest {
209 209
}
210 210
211 211
@Override
212
- public boolean isValid() {
212
+ public boolean isValidCached() {
213 213
return true;
214 214
}
215 215
java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs
... ...
@@ -1,4 +1,3 @@
1 1
eclipse.preferences.version=1
2 2
entryPointModules=
3 3
filesCopiedToWebInfLib=gwt-servlet.jar
4
-gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5SYWNlQm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuQWRtaW5Db25zb2xlPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlNwZWN0YXRvcjwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+
java/com.sap.sailing.gwt.ui/Profile SailingGWT.launch
... ...
@@ -0,0 +1,22 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<launchConfiguration type="com.sap.jvm.profiling.ui.launching.OnlineProfilingLaunchConfigurationType">
3
+<booleanAttribute key="com.sap.jvm.profiling.ui.widgets.allowDebugging" value="false"/>
4
+<booleanAttribute key="com.sap.jvm.profiling.ui.widgets.allowTerminateVm" value="false"/>
5
+<stringAttribute key="com.sap.jvm.profiling.ui.widgets.hostName" value="localhost"/>
6
+<stringAttribute key="com.sap.jvm.profiling.ui.widgets.port" value="7999"/>
7
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
8
+<listEntry value="/com.sap.sailing.gwt.ui"/>
9
+</listAttribute>
10
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
11
+<listEntry value="4"/>
12
+</listAttribute>
13
+<booleanAttribute key="org.eclipse.jdt.launching.ALLOW_TERMINATE" value="false"/>
14
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
15
+<stringAttribute key="org.eclipse.jdt.launching.VM_CONNECTOR_ID" value="org.eclipse.jdt.launching.socketAttachConnector"/>
16
+<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%"/>
17
+<stringAttribute key="profilingTraceType-IO_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
18
+<stringAttribute key="profilingTraceType-METHOD_PARAMETER_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
19
+<stringAttribute key="profilingTraceType-NETWORK_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_RESOLVE_ALL_HOSTS%CTX_KEY%true%CTX_ENTRY%"/>
20
+<stringAttribute key="profilingTraceType-PERFORMANCE_HOTSPOT_TRACE" value="KEY_IGNORE_SLEEPING_THREADS%CTX_KEY%true%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%true%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
21
+<stringAttribute key="profilingTraceType-SYNCHRONIZATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
22
+</launchConfiguration>
java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch
... ...
@@ -0,0 +1,60 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
3
+<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
4
+<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
5
+<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
6
+<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
7
+<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
8
+<listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
9
+</listAttribute>
10
+<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
11
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
12
+<listEntry value="/com.sap.sailing.gwt.ui"/>
13
+</listAttribute>
14
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
15
+<listEntry value="4"/>
16
+</listAttribute>
17
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
18
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
19
+<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
20
+</listAttribute>
21
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
22
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
23
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
24
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
25
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
26
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
27
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
28
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
29
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
30
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
31
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
32
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
33
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
34
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
35
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
36
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
37
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
38
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
39
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
40
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
41
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
42
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
43
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
44
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
45
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
46
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
47
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
48
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
49
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
50
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
51
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
52
+</listAttribute>
53
+<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
54
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
55
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/sapjvm7"/>
56
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
57
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/Home.html com.sap.sailing.gwt.ui.Search -startupUrl /gwt/Home.html com.sap.sailing.gwt.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.VideoPopup"/>
58
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
59
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m -Xdebug -Xrunjdwp:transport=dt_socket,address=7999,server=y"/>
60
+</launchConfiguration>
java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling.launch
... ...
@@ -1,52 +0,0 @@
1
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
-<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
3
-<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
4
-<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
5
-<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
6
-<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
7
-<listEntry value="com.sap.sailing.gwt.ui.RegattaOverview"/>
8
-</listAttribute>
9
-<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
10
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
11
-<listEntry value="/com.sap.sailing.gwt.ui"/>
12
-</listAttribute>
13
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
14
-<listEntry value="4"/>
15
-</listAttribute>
16
-<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
17
-<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
18
-<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
19
-</listAttribute>
20
-<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
21
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/sapjvm_7&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
22
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
23
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
24
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
25
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
26
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
27
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
28
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
29
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
30
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
31
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
32
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
33
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
34
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
35
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
36
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
37
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
38
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
39
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
40
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
41
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
42
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
43
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
44
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
45
-</listAttribute>
46
-<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
47
-<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
48
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
49
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/Home.html com.sap.sailing.gwt.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview"/>
50
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
51
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx1024m -Xdebug -Xrunjdwp:transport=dt_socket,address=7999,server=y"/>
52
-</launchConfiguration>
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResolver.java
... ...
@@ -29,6 +29,7 @@ public class BoatClassImageResolver {
29 29
boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon());
30 30
boatClassIconsMap.put(BoatClassMasterdata.EUROPE_INT.getDisplayName(), imageResources.EuropeIcon());
31 31
boatClassIconsMap.put(BoatClassMasterdata.EXTREME_40.getDisplayName(), imageResources.Extreme40Icon());
32
+ boatClassIconsMap.put(BoatClassMasterdata.D_35.getDisplayName(), imageResources.D35Icon());
32 33
boatClassIconsMap.put(BoatClassMasterdata.F_16.getDisplayName(), imageResources.F16Icon());
33 34
boatClassIconsMap.put(BoatClassMasterdata.F_18.getDisplayName(), imageResources.F18Icon());
34 35
boatClassIconsMap.put(BoatClassMasterdata.FARR_30.getDisplayName(), imageResources.Farr30Icon());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/BoatClassImageResources.java
... ...
@@ -54,7 +54,10 @@ public interface BoatClassImageResources extends ClientBundle {
54 54
55 55
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png")
56 56
ImageResource Extreme40Icon();
57
-
57
+
58
+ @Source("com/sap/sailing/gwt/ui/client/images/boatclass/D35.png")
59
+ ImageResource D35Icon();
60
+
58 61
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/F16.png")
59 62
ImageResource F16Icon();
60 63
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/app/PlaceNavigatorImpl.java
... ...
@@ -17,7 +17,7 @@ import com.sap.sailing.gwt.home.client.place.start.StartPlace;
17 17
18 18
public class PlaceNavigatorImpl implements PlaceNavigator {
19 19
private final PlaceController placeController;
20
- private final static String DEFAULT_SAPSAILING_SERVER = "www.sapsailing.com"; // newhome.sapsailing.com
20
+ private final static String DEFAULT_SAPSAILING_SERVER = "www.sapsailing.com";
21 21
private final static String DEFAULT_SAPSAILING_SERVER_URL = "http://" + DEFAULT_SAPSAILING_SERVER;
22 22
23 23
protected PlaceNavigatorImpl(PlaceController placeController) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/TabletAndDesktopEventView.java
... ...
@@ -182,7 +182,8 @@ public class TabletAndDesktopEventView extends Composite implements EventView, E
182 182
linkParams.put("eventId", event.id.toString());
183 183
linkParams.put("leaderboardName", leaderboardName);
184 184
linkParams.put("raceName", raceIdentifier.getRaceName());
185
- linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true");
185
+ // TODO this must only be forwarded if there is a logged-on user
186
+// linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true");
186 187
linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, "true");
187 188
linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_NAVIGATION_PANEL, "true");
188 189
linkParams.put("regattaName", raceIdentifier.getRegattaName());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/AbstractEventsView.java
... ...
@@ -12,25 +12,25 @@ import com.sap.sailing.gwt.home.client.DateUtil;
12 12
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
13 13
14 14
public abstract class AbstractEventsView extends Composite implements EventsView {
15
- private final Map<Integer, List<EventBaseDTO>> recentEventsOrderedByYear;
15
+ private final Map<Integer, List<EventBaseDTO>> recentEventsByYearOrderedByEndDate;
16 16
private final List<EventBaseDTO> upcomingEvents;
17 17
18 18
public AbstractEventsView() {
19
- recentEventsOrderedByYear = new HashMap<Integer, List<EventBaseDTO>>();
19
+ recentEventsByYearOrderedByEndDate = new HashMap<Integer, List<EventBaseDTO>>();
20 20
upcomingEvents = new ArrayList<EventBaseDTO>();
21 21
}
22 22
23 23
@Override
24 24
public void setEvents(List<EventBaseDTO> events) {
25
- for(EventBaseDTO event: events) {
26
- if(event.startDate != null && event.endDate != null) {
27
- if(DateUtil.isDayInPast(event.startDate) || event.isRunning()) {
25
+ for (EventBaseDTO event : events) {
26
+ if (event.startDate != null && event.endDate != null) {
27
+ if (DateUtil.isDayInPast(event.startDate) || event.isRunning()) {
28 28
// recent event or live event
29 29
int year = DateUtil.getYear(event.startDate);
30
- List<EventBaseDTO> eventsOfYear = recentEventsOrderedByYear.get(year);
31
- if(eventsOfYear == null) {
30
+ List<EventBaseDTO> eventsOfYear = recentEventsByYearOrderedByEndDate.get(year);
31
+ if (eventsOfYear == null) {
32 32
eventsOfYear = new ArrayList<EventBaseDTO>();
33
- recentEventsOrderedByYear.put(year, eventsOfYear);
33
+ recentEventsByYearOrderedByEndDate.put(year, eventsOfYear);
34 34
}
35 35
eventsOfYear.add(event);
36 36
} else {
... ...
@@ -39,8 +39,7 @@ public abstract class AbstractEventsView extends Composite implements EventsView
39 39
}
40 40
}
41 41
}
42
-
43
- for(List<EventBaseDTO> eventsPerYear: recentEventsOrderedByYear.values()) {
42
+ for (List<EventBaseDTO> eventsPerYear : recentEventsByYearOrderedByEndDate.values()) {
44 43
Collections.sort(eventsPerYear, EVENTS_BY_DESCENDING_DATE_COMPARATOR);
45 44
}
46 45
Collections.sort(upcomingEvents, EVENTS_BY_ASCENDING_DATE_COMPARATOR);
... ...
@@ -49,8 +48,13 @@ public abstract class AbstractEventsView extends Composite implements EventsView
49 48
50 49
protected abstract void updateEventsUI();
51 50
52
- public Map<Integer, List<EventBaseDTO>> getRecentEventsOrderedByYear() {
53
- return recentEventsOrderedByYear;
51
+ /**
52
+ * Returns a map whose keys denote years and whose values are event lists ordered by descending
53
+ * {@link EventBaseDTO#endDate end date}. Note that the iteration order for the map entries is undefined
54
+ * and in particular is not enumerating the years in any well-defined order.
55
+ */
56
+ public Map<Integer, List<EventBaseDTO>> getRecentEventsByYearOrderedByEndDate() {
57
+ return recentEventsByYearOrderedByEndDate;
54 58
}
55 59
56 60
public List<EventBaseDTO> getUpcomingEvents() {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/TabletAndDesktopEventsView.java
... ...
@@ -54,7 +54,7 @@ public class TabletAndDesktopEventsView extends AbstractEventsView {
54 54
55 55
@Override
56 56
protected void updateEventsUI() {
57
- recentEventsWidget.updateEvents(getRecentEventsOrderedByYear());
57
+ recentEventsWidget.updateEvents(getRecentEventsByYearOrderedByEndDate());
58 58
upcomingEventsWidget.updateEvents(getUpcomingEvents());
59 59
}
60 60
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecent.java
... ...
@@ -1,9 +1,13 @@
1 1
package com.sap.sailing.gwt.home.client.place.events.recent;
2 2
3
-import java.util.Date;
3
+import java.util.ArrayList;
4
+import java.util.Collections;
4 5
import java.util.HashMap;
6
+import java.util.LinkedHashMap;
5 7
import java.util.List;
8
+import java.util.ListIterator;
6 9
import java.util.Map;
10
+import java.util.Map.Entry;
7 11
8 12
import com.google.gwt.core.client.GWT;
9 13
import com.google.gwt.uibinder.client.UiBinder;
... ...
@@ -11,7 +15,6 @@ import com.google.gwt.uibinder.client.UiField;
11 15
import com.google.gwt.user.client.ui.Composite;
12 16
import com.google.gwt.user.client.ui.HTMLPanel;
13 17
import com.google.gwt.user.client.ui.Widget;
14
-import com.sap.sailing.gwt.home.client.DateUtil;
15 18
import com.sap.sailing.gwt.home.client.app.PlaceNavigator;
16 19
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
17 20
... ...
@@ -36,37 +39,37 @@ public class EventsOverviewRecent extends Composite {
36 39
initWidget(uiBinder.createAndBindUi(this));
37 40
}
38 41
39
- public void updateEvents(Map<Integer, List<EventBaseDTO>> recentEventsOrderedByYear) {
42
+ public void updateEvents(Map<Integer, List<EventBaseDTO>> recentEventsByYearOrderedByEndDate) {
40 43
// remove old widgets
41 44
recentEventsPerYearPanel.clear();
42 45
recentEventsComposites.clear();
43
-
44 46
// recent events of this year
45
- Date now = new Date();
46
- int currentYear = DateUtil.getYear(now);
47
+ LinkedHashMap<Integer, List<EventBaseDTO>> recentEventsOrderedDescendingByYearOrderedDescendingByEndDate =
48
+ sortEventsDescendingByYear(recentEventsByYearOrderedByEndDate);
47 49
boolean oneYearIsExpanded = false;
48
-
49
- if(recentEventsOrderedByYear.get(currentYear) != null) {
50
- EventsOverviewRecentYear recentEventsOfOneYear = new EventsOverviewRecentYear(currentYear, recentEventsOrderedByYear.get(currentYear), navigator);
50
+ for (Entry<Integer, List<EventBaseDTO>> e : recentEventsOrderedDescendingByYearOrderedDescendingByEndDate.entrySet()) {
51
+ int currentYear = e.getKey();
52
+ EventsOverviewRecentYear recentEventsOfOneYear = new EventsOverviewRecentYear(currentYear, e.getValue(), navigator);
51 53
recentEventsPerYearPanel.add(recentEventsOfOneYear);
52 54
recentEventsComposites.put(currentYear, recentEventsOfOneYear);
53
- recentEventsOfOneYear.showContent();
54
- oneYearIsExpanded = true;
55
+ if (oneYearIsExpanded == true) {
56
+ recentEventsOfOneYear.hideContent();
57
+ } else {
58
+ recentEventsOfOneYear.showContent();
59
+ oneYearIsExpanded = true;
60
+ }
55 61
}
56
- currentYear--;
57
- while (currentYear > 2010) {
58
- if(recentEventsOrderedByYear.get(currentYear) != null) {
59
- EventsOverviewRecentYear recentEventsOfOneYear = new EventsOverviewRecentYear(currentYear, recentEventsOrderedByYear.get(currentYear), navigator);
60
- recentEventsPerYearPanel.add(recentEventsOfOneYear);
61
- recentEventsComposites.put(currentYear, recentEventsOfOneYear);
62
- if(oneYearIsExpanded == true) {
63
- recentEventsOfOneYear.hideContent();
64
- } else {
65
- recentEventsOfOneYear.showContent();
66
- oneYearIsExpanded = true;
67
- }
68
- }
69
- currentYear--;
62
+ }
63
+
64
+ private LinkedHashMap<Integer, List<EventBaseDTO>> sortEventsDescendingByYear(
65
+ Map<Integer, List<EventBaseDTO>> recentEventsByYearOrderedByEndDate) {
66
+ LinkedHashMap<Integer, List<EventBaseDTO>> result = new LinkedHashMap<>();
67
+ List<Integer> yearKeysInDescendingOrder = new ArrayList<>(recentEventsByYearOrderedByEndDate.keySet());
68
+ Collections.sort(yearKeysInDescendingOrder);
69
+ for (ListIterator<Integer> i=yearKeysInDescendingOrder.listIterator(yearKeysInDescendingOrder.size()); i.hasPrevious(); ) {
70
+ Integer year = i.previous();
71
+ result.put(year, recentEventsByYearOrderedByEndDate.get(year));
70 72
}
73
+ return result;
71 74
}
72 75
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/recent/EventsOverviewRecentYear.java
... ...
@@ -37,20 +37,16 @@ public class EventsOverviewRecentYear extends Composite {
37 37
public EventsOverviewRecentYear(Integer year, List<EventBaseDTO> events, PlaceNavigator navigator) {
38 38
EventsOverviewRecentResources.INSTANCE.css().ensureInjected();
39 39
initWidget(uiBinder.createAndBindUi(this));
40
-
41 40
this.year.setInnerText(String.valueOf(year));
42 41
this.eventsCount.setInnerText(String.valueOf(events.size()));
43 42
// this.countriesCount.setInnerText("tbd.");
44 43
// this.sailorsCount.setInnerText("tbd.");
45 44
// this.trackedRacesCount.setInnerText("tbd.");
46
-
47
- for(EventBaseDTO eventDTO: events) {
48
- RecentEvent recentEvent = new RecentEvent(navigator);
49
- recentEvent.setEvent(eventDTO);
45
+ for (EventBaseDTO eventDTO : events) {
46
+ RecentEvent recentEvent = new RecentEvent(navigator, eventDTO);
50 47
recentEventsTeaserPanel.appendChild(recentEvent.getElement());
51 48
}
52 49
isContentVisible = true;
53
-
54 50
headerDiv.addDomHandler(new ClickHandler() {
55 51
@Override
56 52
public void onClick(ClickEvent event) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/events/upcoming/EventsOverviewUpcoming.java
... ...
@@ -28,7 +28,6 @@ public class EventsOverviewUpcoming extends Composite {
28 28
public EventsOverviewUpcoming(PlaceNavigator navigator) {
29 29
this.navigator = navigator;
30 30
upcomingEventComposites = new ArrayList<UpcomingEvent>();
31
-
32 31
EventsOverviewUpcomingResources.INSTANCE.css().ensureInjected();
33 32
initWidget(uiBinder.createAndBindUi(this));
34 33
}
... ...
@@ -36,10 +35,8 @@ public class EventsOverviewUpcoming extends Composite {
36 35
public void updateEvents(List<EventBaseDTO> events) {
37 36
eventsPlaceholder.clear();
38 37
upcomingEventComposites.clear();
39
-
40
- for(EventBaseDTO event: events) {
38
+ for (EventBaseDTO event : events) {
41 39
UpcomingEvent upcomingEvent = new UpcomingEvent(event, navigator);
42
-
43 40
upcomingEventComposites.add(upcomingEvent);
44 41
eventsPlaceholder.getElement().appendChild(upcomingEvent.getElement());
45 42
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/SmartphoneStartView.java
... ...
@@ -45,4 +45,9 @@ public class SmartphoneStartView extends Composite implements StartView {
45 45
mainEvents.setRecentEvents(recentEvents);
46 46
mainMedia.setRecentEvents(recentEvents);
47 47
}
48
+
49
+ @Override
50
+ public void adjustSizes() {
51
+ stage.adjustSize();
52
+ }
48 53
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java
... ...
@@ -7,9 +7,12 @@ import java.util.Date;
7 7
import java.util.List;
8 8
9 9
import com.google.gwt.activity.shared.AbstractActivity;
10
+import com.google.gwt.core.client.Scheduler;
11
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
10 12
import com.google.gwt.event.shared.EventBus;
11 13
import com.google.gwt.user.client.rpc.AsyncCallback;
12 14
import com.google.gwt.user.client.ui.AcceptsOneWidget;
15
+import com.sap.sailing.gwt.home.client.DateUtil;
13 16
import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder;
14 17
import com.sap.sailing.gwt.home.client.shared.stage.StageEventType;
15 18
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
... ...
@@ -25,13 +28,11 @@ public class StartActivity extends AbstractActivity {
25 28
@Override
26 29
public void start(final AcceptsOneWidget panel, EventBus eventBus) {
27 30
panel.setWidget(new Placeholder());
28
-
29 31
clientFactory.getSailingService().getPublicEventsOfAllSailingServers(new AsyncCallback<List<EventBaseDTO>>() {
30 32
@Override
31 33
public void onSuccess(List<EventBaseDTO> result) {
32 34
final StartView view = clientFactory.createStartView();
33 35
panel.setWidget(view.asWidget());
34
-
35 36
fillStartPageEvents(view, result);
36 37
}
37 38
... ...
@@ -41,71 +42,76 @@ public class StartActivity extends AbstractActivity {
41 42
});
42 43
}
43 44
44
- @SuppressWarnings("deprecation")
45
- protected void fillStartPageEvents(StartView view, List<EventBaseDTO> events) {
45
+ protected void fillStartPageEvents(final StartView view, List<EventBaseDTO> events) {
46 46
List<Pair<StageEventType, EventBaseDTO>> featuredEvents = new ArrayList<Pair<StageEventType, EventBaseDTO>>();
47
-
48 47
List<EventBaseDTO> recentEventsOfSameYear = new ArrayList<EventBaseDTO>();
49 48
List<EventBaseDTO> upcomingSoonEvents = new ArrayList<EventBaseDTO>();
50 49
List<EventBaseDTO> popularEvents = new ArrayList<EventBaseDTO>();
51 50
Date now = new Date();
52
- int currentYear = now.getYear();
53
- final int MAX_STAGE_EVENTS = 2;
51
+ int currentYear = DateUtil.getYear(now);
52
+ final int MAX_STAGE_EVENTS = 5;
54 53
final long FOUR_WEEK_IN_MS = 4L * (1000 * 60 * 60 * 24 * 7);
55
-
56
- for(EventBaseDTO event: events) {
57
- if(event.startDate != null && event.endDate != null) {
54
+ for (EventBaseDTO event : events) {
55
+ if (event.startDate != null && event.endDate != null) {
58 56
if (now.after(event.startDate) && now.before(event.endDate)) {
59 57
featuredEvents.add(new Pair<StageEventType, EventBaseDTO>(StageEventType.RUNNING, event));
60 58
} else if (event.startDate.after(now) && event.startDate.getTime() - now.getTime() < FOUR_WEEK_IN_MS) {
61 59
upcomingSoonEvents.add(event);
62
- } else if (event.endDate.before(now) && event.endDate.getYear() == currentYear) {
60
+ } else if (event.endDate.before(now) && DateUtil.getYear(event.endDate) == currentYear) {
63 61
recentEventsOfSameYear.add(event);
64 62
}
65 63
}
66 64
}
67
-
68
- if(featuredEvents.size() < MAX_STAGE_EVENTS) {
65
+ if (featuredEvents.size() < MAX_STAGE_EVENTS) {
69 66
fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.UPCOMING_SOON, upcomingSoonEvents);
70 67
}
71
- if(featuredEvents.size() < MAX_STAGE_EVENTS) {
68
+ if (featuredEvents.size() < MAX_STAGE_EVENTS) {
72 69
fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.POPULAR, popularEvents);
73 70
}
74 71
// fallback for the case we did not find any events
75
- if(featuredEvents.size() < MAX_STAGE_EVENTS) {
72
+ if (featuredEvents.size() < MAX_STAGE_EVENTS) {
76 73
fillingUpEventsList(MAX_STAGE_EVENTS, featuredEvents, StageEventType.POPULAR, recentEventsOfSameYear);
77 74
}
78
-
79 75
Collections.sort(featuredEvents, new FeaturedEventsComparator());
80
-
81 76
view.setFeaturedEvents(featuredEvents);
82 77
view.setRecentEvents(recentEventsOfSameYear);
78
+ // See bug 2232: the stage image sizes are scaled incorrectly. https://github.com/ubilabs/sap-sailing-analytics/issues/421 and
79
+ // http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2232 have the details. A quick fix may be to send a resize event
80
+ // after everything has been rendered.
81
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
82
+ @Override
83
+ public void execute() {
84
+ view.adjustSizes();
85
+ }
86
+ });
83 87
}
84 88
85
- private void fillingUpEventsList(int maxAmountOfElements, List<Pair<StageEventType, EventBaseDTO>> resultList, StageEventType type, List<EventBaseDTO> listToTakeElementsFrom) {
89
+ private void fillingUpEventsList(int maxAmountOfElements, List<Pair<StageEventType, EventBaseDTO>> resultList,
90
+ StageEventType type, List<EventBaseDTO> listToTakeElementsFrom) {
86 91
int maxElementsToFill = maxAmountOfElements - resultList.size();
87
- int elementsToTransfer = listToTakeElementsFrom.size() > maxElementsToFill ? maxElementsToFill : listToTakeElementsFrom.size();
88
- for(int i = 0; i < elementsToTransfer; i++) {
92
+ int elementsToTransfer = listToTakeElementsFrom.size() > maxElementsToFill ? maxElementsToFill
93
+ : listToTakeElementsFrom.size();
94
+ for (int i = 0; i < elementsToTransfer; i++) {
89 95
resultList.add(new Pair<StageEventType, EventBaseDTO>(type, listToTakeElementsFrom.get(i)));
90 96
}
91 97
}
92
-
98
+
93 99
/**
94
- * Comparator for sorting a pair of event and stageType first by order number of stage type and then by event start date.
100
+ * Comparator for sorting a pair of event and stageType first by order number of stage type and then by event start
101
+ * date.
102
+ *
95 103
* @author Frank
96
- *
97 104
*/
98 105
private class FeaturedEventsComparator implements Comparator<Pair<StageEventType, EventBaseDTO>> {
99
-
100 106
@Override
101
- public int compare(Pair<StageEventType, EventBaseDTO> eventAndStageType1, Pair<StageEventType, EventBaseDTO> eventAndStageType2) {
107
+ public int compare(Pair<StageEventType, EventBaseDTO> eventAndStageType1,
108
+ Pair<StageEventType, EventBaseDTO> eventAndStageType2) {
102 109
int result;
103
- if(eventAndStageType1.getA().ordinal() == eventAndStageType2.getA().ordinal()) {
104
- result = eventAndStageType1.getB().startDate.compareTo(eventAndStageType2.getB().startDate);
110
+ if (eventAndStageType1.getA().ordinal() == eventAndStageType2.getA().ordinal()) {
111
+ result = eventAndStageType1.getB().startDate.compareTo(eventAndStageType2.getB().startDate);
105 112
} else {
106
- result = eventAndStageType1.getA().ordinal() - eventAndStageType2.getA().ordinal();
113
+ result = eventAndStageType1.getA().ordinal() - eventAndStageType2.getA().ordinal();
107 114
}
108
-
109 115
return result;
110 116
}
111 117
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartView.java
... ...
@@ -13,4 +13,6 @@ public interface StartView {
13 13
void setFeaturedEvents(List<Pair<StageEventType, EventBaseDTO>> featuredEvents);
14 14
15 15
void setRecentEvents(List<EventBaseDTO> recentEvents);
16
+
17
+ void adjustSizes();
16 18
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/TabletAndDesktopStartView.java
... ...
@@ -47,4 +47,9 @@ public class TabletAndDesktopStartView extends Composite implements StartView {
47 47
mainMedia.setRecentEvents(recentEvents);
48 48
}
49 49
50
+ @Override
51
+ public void adjustSizes() {
52
+ stage.adjustSize();
53
+ }
54
+
50 55
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/leaderboard/LeaderboardViewer.java
... ...
@@ -43,8 +43,8 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer {
43 43
super(competitorSelectionModel, asyncActionsExecutor, timer, stringMessages, new LeaderboardPanel(
44 44
sailingService, asyncActionsExecutor, leaderboardSettings, preselectedRace,
45 45
competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter,
46
- stringMessages, userAgent, showRaceDetails, /* raceTimesInfoProvider */null, /*showSelectionCheckbox*/false, null,
47
- autoExpandLastRaceColumn, /* adjustTimerDelay */true, false, false));
46
+ stringMessages, userAgent, showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn, /* adjustTimerDelay */
47
+ true, false, false));
48 48
final FlowPanel mainPanel = new FlowPanel();
49 49
setWidget(mainPanel);
50 50
... ...
@@ -61,8 +61,8 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer {
61 61
overallLeaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor,
62 62
leaderboardSettings, preselectedRace, competitorSelectionProvider, timer,
63 63
leaderboardGroupName, overallLeaderboardName, errorReporter, stringMessages, userAgent,
64
- /*showRaceDetails*/false, null, /*showSelectionCheckbox*/true, null, /*autoExpandLastRaceColumn*/false,
65
- /*adjustTimerDelay*/true, /*autoApplyTopNFilter*/false, false);
64
+ false, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null, false,
65
+ /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/false, false);
66 66
mainPanel.add(overallLeaderboardPanel);
67 67
addComponentToNavigationMenu(overallLeaderboardPanel, true, stringMessages.seriesLeaderboard(),
68 68
/* hasSettingsWhenComponentIsInvisible*/ true);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/leaderboard/MetaLeaderboardViewer.java
... ...
@@ -42,7 +42,8 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer {
42 42
super(competitorSelectionModel, asyncActionsExecutor, timer, stringMessages, new LeaderboardPanel(sailingService, asyncActionsExecutor,
43 43
leaderboardSettings, preselectedRace, competitorSelectionModel, timer,
44 44
leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, userAgent,
45
- showRaceDetails, /* raceTimesInfoProvider */null, false, null, autoExpandLastRaceColumn, /* adjustTimerDelay */ true, false, false));
45
+ showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null, autoExpandLastRaceColumn,
46
+ /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/false, false));
46 47
final FlowPanel mainPanel = new FlowPanel();
47 48
setWidget(mainPanel);
48 49
final Label overallStandingsLabel = new Label(stringMessages.overallStandings());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainevents/MainEvents.java
... ...
@@ -39,25 +39,17 @@ public class MainEvents extends Composite {
39 39
}
40 40
41 41
public void setRecentEvents(List<EventBaseDTO> theRecentEvents) {
42
+ final int MAX_RECENT_EVENTS_ON_HOME_PAGE = 3;
42 43
recentEventsDiv.removeAllChildren();
43 44
recentEvents.clear();
44 45
recentEvents.addAll(theRecentEvents);
45
-
46
- int size = recentEvents.size();
47
- if(size > 0) {
48
- createRecentEvent(recentEvents.get(0));
49
- }
50
- if(size > 1) {
51
- createRecentEvent(recentEvents.get(1));
52
- }
53
- if(size > 2) {
54
- createRecentEvent(recentEvents.get(2));
46
+ for (int i=0; i<recentEvents.size() && i<MAX_RECENT_EVENTS_ON_HOME_PAGE; i++) {
47
+ createRecentEvent(recentEvents.get(i));
55 48
}
56 49
}
57 50
58 51
private void createRecentEvent(EventBaseDTO eventBase) {
59
- RecentEvent event = new RecentEvent(navigator);
60
- event.setEvent(eventBase);
52
+ RecentEvent event = new RecentEvent(navigator, eventBase);
61 53
recentEventsDiv.appendChild(event.getElement());
62 54
}
63 55
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/mainmedia/MainMedia.java
... ...
@@ -6,6 +6,8 @@ import java.util.List;
6 6
import java.util.Random;
7 7
8 8
import com.google.gwt.core.client.GWT;
9
+import com.google.gwt.core.client.Scheduler;
10
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
9 11
import com.google.gwt.dom.client.DivElement;
10 12
import com.google.gwt.event.dom.client.ClickEvent;
11 13
import com.google.gwt.uibinder.client.UiBinder;
... ...
@@ -24,6 +26,8 @@ import com.sap.sse.common.Util.Pair;
24 26
25 27
public class MainMedia extends Composite {
26 28
29
+ private static final int MAX_VIDEO_COUNT = 3;
30
+
27 31
private static final MainMediaResources.LocalCss STYLES = MainMediaResources.INSTANCE.css();
28 32
29 33
@UiField HTMLPanel videosPanel;
... ...
@@ -48,7 +52,7 @@ public class MainMedia extends Composite {
48 52
49 53
public void setFeaturedEvents(List<Pair<StageEventType, EventBaseDTO>> featuredEvents) {
50 54
for(Pair<StageEventType, EventBaseDTO> featuredEventTypeAndEvent: featuredEvents) {
51
- if(featuredEventTypeAndEvent.getB().getVideoURLs().size() > 0 && videoCounter < 3) {
55
+ if(featuredEventTypeAndEvent.getB().getVideoURLs().size() > 0 && videoCounter < MAX_VIDEO_COUNT) {
52 56
addVideoToVideoPanel(featuredEventTypeAndEvent.getB());
53 57
}
54 58
}
... ...
@@ -56,31 +60,35 @@ public class MainMedia extends Composite {
56 60
57 61
public void setRecentEvents(List<EventBaseDTO> recentEvents) {
58 62
List<String> photoGalleryUrls = new ArrayList<String>();
59
-
60
- for(EventBaseDTO event: recentEvents) {
63
+ for (EventBaseDTO event : recentEvents) {
61 64
photoGalleryUrls.addAll(event.getPhotoGalleryImageURLs());
62
-
63
- if(event.getVideoURLs().size() > 0 && videoCounter < 3) {
65
+ if (!event.getVideoURLs().isEmpty() && videoCounter < MAX_VIDEO_COUNT) {
64 66
addVideoToVideoPanel(event);
65 67
}
66 68
}
67
-
68 69
// shuffle the image url list (Remark: Collections.shuffle() is not implemented in GWT)
69 70
int gallerySize = photoGalleryUrls.size();
70
- Random random = new Random(gallerySize);
71
- for(int i = 0; i < gallerySize; i++) {
72
- Collections.swap(photoGalleryUrls, i, random.nextInt(gallerySize));
71
+ Random random = new Random(gallerySize);
72
+ for (int i = 0; i < gallerySize; i++) {
73
+ Collections.swap(photoGalleryUrls, i, random.nextInt(gallerySize));
73 74
}
74
-
75
- for(String url : photoGalleryUrls) {
75
+ for (String url : photoGalleryUrls) {
76 76
SimplePanel imageContainer = new SimplePanel();
77 77
imageContainer.addStyleName(STYLES.media_swiperslide());
78 78
String image = "url(" + url + ")";
79 79
imageContainer.getElement().getStyle().setBackgroundImage(image);
80 80
mediaSlides.add(imageContainer);
81 81
}
82
-
83 82
this.swiper = Swiper.createWithLoopOption(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide());
83
+ // See bug 2232: the stage image sizes are scaled incorrectly. https://github.com/ubilabs/sap-sailing-analytics/issues/421 and
84
+ // http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2232 have the details. A quick fix may be to send a resize event
85
+ // after everything has been rendered.
86
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
87
+ @Override
88
+ public void execute() {
89
+ swiper.reInit();
90
+ }
91
+ });
84 92
}
85 93
86 94
private void addVideoToVideoPanel(EventBaseDTO event) {
... ...
@@ -95,19 +103,12 @@ public class MainMedia extends Composite {
95 103
}
96 104
97 105
private String getRandomVideoURL(EventBaseDTO event) {
98
- String result = null;
99
- int videosCount = event.getVideoURLs().size();
100
- if(videosCount > 0) {
101
- if(videosCount == 1) {
102
- result = event.getVideoURLs().get(0);
103
- } else {
104
- List<String> videoUrls = new ArrayList<String>(event.getVideoURLs());
105
- Random random = new Random(videosCount);
106
- for(int i = 0; i < videosCount; i++) {
107
- Collections.swap(videoUrls, i, random.nextInt(videosCount));
108
- }
109
- result = videoUrls.get(0);
110
- }
106
+ final String result;
107
+ List<String> videoURLs = event.getVideoURLs();
108
+ if (!videoURLs.isEmpty()) {
109
+ result = event.getVideoURLs().get(new Random(videoURLs.size()).nextInt(videoURLs.size()));
110
+ } else {
111
+ result = null;
111 112
}
112 113
return result;
113 114
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/recentevent/RecentEvent.java
... ...
@@ -29,7 +29,7 @@ public class RecentEvent extends UIObject {
29 29
@UiField ImageElement eventImage;
30 30
@UiField DivElement isLiveDiv;
31 31
32
- private EventBaseDTO event;
32
+ private final EventBaseDTO event;
33 33
34 34
private final String defaultImageUrl = "http://static.sapsailing.com/ubilabsimages/default/default_event_photo.jpg";
35 35
... ...
@@ -38,11 +38,10 @@ public class RecentEvent extends UIObject {
38 38
39 39
private static RecentEventUiBinder uiBinder = GWT.create(RecentEventUiBinder.class);
40 40
41
- public RecentEvent(final PlaceNavigator navigator) {
41
+ public RecentEvent(final PlaceNavigator navigator, final EventBaseDTO event) {
42
+ this.event = event;
42 43
RecentEventResources.INSTANCE.css().ensureInjected();
43
-
44 44
setElement(uiBinder.createAndBindUi(this));
45
-
46 45
Event.sinkEvents(eventOverviewLink, Event.ONCLICK);
47 46
Event.setEventListener(eventOverviewLink, new EventListener() {
48 47
@Override
... ...
@@ -54,27 +53,19 @@ public class RecentEvent extends UIObject {
54 53
}
55 54
}
56 55
});
57
-
58
- }
59
-
60
- public void setEvent(EventBaseDTO event) {
61
- this.event = event;
62 56
updateUI();
63 57
}
64 58
65 59
private void updateUI() {
66 60
SafeHtml safeHtmlEventName = LongNamesUtil.breakLongName(event.getName());
67
- eventName.setInnerSafeHtml(safeHtmlEventName);
68
-
69
- if(!event.isRunning()) {
61
+ eventName.setInnerSafeHtml(safeHtmlEventName);
62
+ if (!event.isRunning()) {
70 63
isLiveDiv.getStyle().setDisplay(Display.NONE);
71 64
}
72
-
73 65
venueName.setInnerText(event.venue.getName());
74 66
eventStartDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithoutYear(event.startDate, event.endDate));
75
-
76 67
List<String> photoGalleryImageURLs = event.getPhotoGalleryImageURLs();
77
- if(photoGalleryImageURLs.size() == 0) {
68
+ if (photoGalleryImageURLs.isEmpty()) {
78 69
eventImage.setSrc(defaultImageUrl);
79 70
} else {
80 71
eventImage.setSrc(photoGalleryImageURLs.get(0));
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/shared/stage/Stage.java
... ...
@@ -87,6 +87,10 @@ public class Stage extends Composite {
87 87
prevStageTeaserLink.setVisible(false);
88 88
}
89 89
}
90
+
91
+ public void adjustSize() {
92
+ swiper.reInit();
93
+ }
90 94
91 95
@UiHandler("nextStageTeaserLink")
92 96
public void nextStageTeaserLinkClicked(ClickEvent e) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/idangerous/Swiper.java
... ...
@@ -31,7 +31,7 @@ public final class Swiper extends JavaScriptObject {
31 31
var options = {
32 32
loop: loop,
33 33
wrapperClass: wrapperClass,
34
- slideClass: slideClass
34
+ slideClass: slideClass,
35 35
};
36 36
if (pageChangeListener) {
37 37
options.onSlideChangeEnd = function(swiper) {
... ...
@@ -43,6 +43,10 @@ public final class Swiper extends JavaScriptObject {
43 43
return new $wnd.Swiper('.'+containerClass, options);
44 44
}-*/;
45 45
46
+ public native void reInit() /*-{
47
+ this.reInit(true);
48
+ }-*/;
49
+
46 50
public native void swipeNext() /*-{
47 51
this.swipeNext();
48 52
}-*/;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java
... ...
@@ -1,6 +1,7 @@
1 1
package com.sap.sailing.gwt.ui.adminconsole;
2 2
3 3
import java.util.ArrayList;
4
+import java.util.Comparator;
4 5
import java.util.List;
5 6
import java.util.Map;
6 7
... ...
@@ -8,6 +9,7 @@ import com.google.gwt.core.client.GWT;
8 9
import com.google.gwt.event.dom.client.ClickEvent;
9 10
import com.google.gwt.event.dom.client.ClickHandler;
10 11
import com.google.gwt.user.cellview.client.CellTable;
12
+import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
11 13
import com.google.gwt.user.client.Window;
12 14
import com.google.gwt.user.client.rpc.AsyncCallback;
13 15
import com.google.gwt.user.client.ui.Button;
... ...
@@ -37,6 +39,7 @@ import com.sap.sailing.gwt.ui.client.RegattaRefresher;
37 39
import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
38 40
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
39 41
import com.sap.sailing.gwt.ui.client.StringMessages;
42
+import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
40 43
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
41 44
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
42 45
import com.sap.sse.common.Util;
... ...
@@ -157,17 +160,14 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement
157 160
}
158 161
};
159 162
leaderboardConfigControlsPanel.add(filterLeaderboardPanel);
160
-
161 163
addLeaderboardConfigControls(leaderboardConfigControlsPanel);
162
-
163 164
leaderboardsPanel.add(leaderboardConfigControlsPanel);
164 165
leaderboardTable.ensureDebugId("AvailableLeaderboardsTable");
165
-
166
- addColumnsToLeaderboardTable(leaderboardTable);
167
-
166
+ addColumnsToLeaderboardTableAndSetSelectionModel(leaderboardTable, tableRes);
167
+ @SuppressWarnings("unchecked")
168
+ MultiSelectionModel<StrippedLeaderboardDTO> multiSelectionModel = (MultiSelectionModel<StrippedLeaderboardDTO>) leaderboardTable.getSelectionModel();
169
+ leaderboardSelectionModel = multiSelectionModel;
168 170
leaderboardTable.setWidth("100%");
169
- leaderboardSelectionModel = new MultiSelectionModel<StrippedLeaderboardDTO>();
170
- leaderboardTable.setSelectionModel(leaderboardSelectionModel);
171 171
leaderboardSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
172 172
public void onSelectionChange(SelectionChangeEvent event) {
173 173
leaderboardSelectionChanged();
... ...
@@ -179,10 +179,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement
179 179
HorizontalPanel leaderboardButtonPanel = new HorizontalPanel();
180 180
leaderboardButtonPanel.setSpacing(5);
181 181
leaderboardsPanel.add(leaderboardButtonPanel);
182
-
183 182
addLeaderboardCreateControls(leaderboardButtonPanel);
184
-
185
-
186 183
mainPanel.add(new Grid(1, 1));
187 184
188 185
// caption panels for the selected leaderboard and tracked races
... ...
@@ -257,9 +254,33 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement
257 254
protected abstract void addLeaderboardConfigControls(Panel configPanel);
258 255
protected abstract void addLeaderboardCreateControls(Panel createPanel);
259 256
protected abstract void addSelectedLeaderboardRacesControls(Panel racesPanel);
260
- protected abstract void addColumnsToLeaderboardTable(CellTable<StrippedLeaderboardDTO> leaderboardTable);
257
+ protected abstract void addColumnsToLeaderboardTableAndSetSelectionModel(CellTable<StrippedLeaderboardDTO> leaderboardTable, AdminConsoleTableResources tableRes);
261 258
protected abstract void addColumnsToRacesTable(CellTable<RaceColumnDTOAndFleetDTOWithNameBasedEquality> racesTable);
262 259
260
+ protected SelectionCheckboxColumn<StrippedLeaderboardDTO> createSortableSelectionCheckboxColumn(
261
+ final CellTable<StrippedLeaderboardDTO> leaderboardTable, AdminConsoleTableResources tableResources,
262
+ ListHandler<StrippedLeaderboardDTO> leaderboardColumnListHandler) {
263
+ SelectionCheckboxColumn<StrippedLeaderboardDTO> selectionCheckboxColumn = new SelectionCheckboxColumn<StrippedLeaderboardDTO>(tableResources.cellTableStyle().cellTableCheckboxSelected(),
264
+ tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) {
265
+ @Override
266
+ protected ListDataProvider<StrippedLeaderboardDTO> getListDataProvider() {
267
+ return leaderboardList;
268
+ }
269
+
270
+ @Override
271
+ public Boolean getValue(StrippedLeaderboardDTO row) {
272
+ return leaderboardTable.getSelectionModel().isSelected(row);
273
+ }
274
+ };
275
+ selectionCheckboxColumn.setSortable(true);
276
+ leaderboardColumnListHandler.setComparator(selectionCheckboxColumn, new Comparator<StrippedLeaderboardDTO>() {
277
+ @Override
278
+ public int compare(StrippedLeaderboardDTO o1, StrippedLeaderboardDTO o2) {
279
+ return (leaderboardTable.getSelectionModel().isSelected(o1) ? 1 : 0) - (leaderboardTable.getSelectionModel().isSelected(o2) ? 1 : 0);
280
+ }
281
+ });
282
+ return selectionCheckboxColumn;
283
+ }
263 284
264 285
@Override
265 286
public void fillLeaderboards(Iterable<StrippedLeaderboardDTO> leaderboards) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java
... ...
@@ -832,8 +832,10 @@ public class EventManagementPanel extends SimplePanel implements EventsRefresher
832 832
for (CourseAreaDTO courseAreaDTO : newEvent.venue.getCourseAreas()) {
833 833
courseAreaNames.add(courseAreaDTO.getName());
834 834
}
835
- sailingService.createEvent(newEvent.getName(), newEvent.startDate, newEvent.endDate, newEvent.venue.getName(),
836
- newEvent.isPublic, courseAreaNames, newEvent.getImageURLs(), newEvent.getVideoURLs(), new AsyncCallback<EventDTO>() {
835
+ sailingService.createEvent(newEvent.getName(), newEvent.getDescription(), newEvent.startDate, newEvent.endDate,
836
+ newEvent.venue.getName(), newEvent.isPublic, courseAreaNames, newEvent.getImageURLs(),
837
+ newEvent.getVideoURLs(), newEvent.getSponsorImageURLs(), newEvent.getLogoImageURL(),
838
+ newEvent.getOfficialWebsiteURL(), new AsyncCallback<EventDTO>() {
837 839
@Override
838 840
public void onFailure(Throwable t) {
839 841
errorReporter.reportError("Error trying to create new event " + newEvent.getName() + ": " + t.getMessage());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java
... ...
@@ -48,6 +48,7 @@ import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
48 48
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
49 49
import com.sap.sailing.gwt.ui.client.StringMessages;
50 50
import com.sap.sailing.gwt.ui.client.URLEncoder;
51
+import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
51 52
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint;
52 53
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
53 54
import com.sap.sailing.gwt.ui.shared.EventDTO;
... ...
@@ -113,10 +114,11 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
113 114
}
114 115
115 116
@Override
116
- protected void addColumnsToLeaderboardTable(final CellTable<StrippedLeaderboardDTO> leaderboardTable) {
117
+ protected void addColumnsToLeaderboardTableAndSetSelectionModel(final CellTable<StrippedLeaderboardDTO> leaderboardTable, AdminConsoleTableResources tableResources) {
117 118
ListHandler<StrippedLeaderboardDTO> leaderboardColumnListHandler = new ListHandler<StrippedLeaderboardDTO>(
118 119
leaderboardList.getList());
119
-
120
+ SelectionCheckboxColumn<StrippedLeaderboardDTO> selectionCheckboxColumn = createSortableSelectionCheckboxColumn(
121
+ leaderboardTable, tableResources, leaderboardColumnListHandler);
120 122
AnchorCell anchorCell = new AnchorCell();
121 123
Column<StrippedLeaderboardDTO, SafeHtml> linkColumn = new Column<StrippedLeaderboardDTO, SafeHtml>(anchorCell) {
122 124
@Override
... ...
@@ -262,6 +264,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
262 264
}
263 265
}
264 266
});
267
+ leaderboardTable.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader());
265 268
leaderboardTable.addColumn(linkColumn, stringMessages.name());
266 269
leaderboardTable.addColumn(leaderboardDisplayNameColumn, stringMessages.displayName());
267 270
leaderboardTable.addColumn(discardingOptionsColumn, stringMessages.discarding());
... ...
@@ -270,8 +273,9 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
270 273
leaderboardTable.addColumn(courseAreaColumn, stringMessages.courseArea());
271 274
leaderboardTable.addColumn(leaderboardActionColumn, stringMessages.actions());
272 275
leaderboardTable.addColumnSortHandler(leaderboardColumnListHandler);
276
+ leaderboardTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager());
273 277
}
274
-
278
+
275 279
protected void addLeaderboardCreateControls(Panel createPanel) {
276 280
Button createFlexibleLeaderboardBtn = new Button(stringMessages.createFlexibleLeaderboard() + "...");
277 281
createFlexibleLeaderboardBtn.ensureDebugId("CreateFlexibleLeaderboardButton");
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java
... ...
@@ -2,7 +2,6 @@ package com.sap.sailing.gwt.ui.adminconsole;
2 2
3 3
import java.util.Collection;
4 4
import java.util.Comparator;
5
-import java.util.Date;
6 5
7 6
import com.google.gwt.cell.client.EditTextCell;
8 7
import com.google.gwt.cell.client.FieldUpdater;
... ...
@@ -23,6 +22,8 @@ import com.google.gwt.view.client.DefaultSelectionEventManager;
23 22
import com.google.gwt.view.client.ListDataProvider;
24 23
import com.google.gwt.view.client.SelectionModel;
25 24
import com.google.gwt.view.client.SingleSelectionModel;
25
+import com.sap.sailing.domain.common.TimePoint;
26
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
26 27
import com.sap.sailing.domain.common.media.MediaTrack;
27 28
import com.sap.sailing.domain.common.media.MediaUtil;
28 29
import com.sap.sailing.gwt.ui.client.ErrorReporter;
... ...
@@ -225,7 +226,7 @@ public class MediaPanel extends FlowPanel {
225 226
Column<MediaTrack, String> startTimeColumn = new Column<MediaTrack, String>(new EditTextCell()) {
226 227
@Override
227 228
public String getValue(MediaTrack mediaTrack) {
228
- return mediaTrack.startTime == null ? "" : TimeFormatUtil.DATETIME_FORMAT.format(mediaTrack.startTime);
229
+ return mediaTrack.startTime == null ? "" : TimeFormatUtil.DATETIME_FORMAT.format(mediaTrack.startTime.asDate());
229 230
}
230 231
};
231 232
startTimeColumn.setSortable(true);
... ...
@@ -242,7 +243,7 @@ public class MediaPanel extends FlowPanel {
242 243
mediaTrack.startTime = null;
243 244
} else {
244 245
try {
245
- mediaTrack.startTime = TimeFormatUtil.DATETIME_FORMAT.parse(newStartTime);
246
+ mediaTrack.startTime = new MillisecondsTimePoint(TimeFormatUtil.DATETIME_FORMAT.parse(newStartTime));
246 247
} catch (IllegalArgumentException e) {
247 248
errorReporter.reportError(stringMessages.mediaDateFormatError(TimeFormatUtil.DATETIME_FORMAT.toString()));
248 249
}
... ...
@@ -268,19 +269,19 @@ public class MediaPanel extends FlowPanel {
268 269
Column<MediaTrack, String> durationColumn = new Column<MediaTrack, String>(new EditTextCell()) {
269 270
@Override
270 271
public String getValue(MediaTrack mediaTrack) {
271
- return TimeFormatUtil.milliSecondsToHrsMinSec(mediaTrack.durationInMillis);
272
+ return TimeFormatUtil.durationToHrsMinSec(mediaTrack.duration);
272 273
}
273 274
};
274 275
durationColumn.setSortable(true);
275 276
sortHandler.setComparator(durationColumn, new Comparator<MediaTrack>() {
276 277
public int compare(MediaTrack mediaTrack1, MediaTrack mediaTrack2) {
277
- return Integer.valueOf(mediaTrack1.durationInMillis).compareTo(Integer.valueOf(mediaTrack2.durationInMillis));
278
+ return mediaTrack1.duration.compareTo(mediaTrack2.duration);
278 279
}
279 280
});
280 281
durationColumn.setFieldUpdater(new FieldUpdater<MediaTrack, String>() {
281 282
public void update(int index, MediaTrack mediaTrack, String newDuration) {
282 283
// Called when the user changes the value.
283
- mediaTrack.durationInMillis = TimeFormatUtil.hrsMinSecToMilliSeconds(newDuration);
284
+ mediaTrack.duration = TimeFormatUtil.hrsMinSecToMilliSeconds(newDuration);
284 285
mediaService.updateDuration(mediaTrack, new AsyncCallback<Void>() {
285 286
286 287
@Override
... ...
@@ -332,7 +333,7 @@ public class MediaPanel extends FlowPanel {
332 333
}
333 334
334 335
private void addUrlMediaTrack() {
335
- Date defaultStartTime = new Date();
336
+ TimePoint defaultStartTime = MillisecondsTimePoint.now();
336 337
NewMediaDialog dialog = new NewMediaDialog(defaultStartTime , stringMessages, new DialogCallback<MediaTrack>() {
337 338
338 339
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java
... ...
@@ -30,6 +30,7 @@ import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher;
30 30
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
31 31
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
32 32
import com.sap.sailing.gwt.ui.client.StringMessages;
33
+import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
33 34
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
34 35
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
35 36
import com.sap.sse.common.Util;
... ...
@@ -62,24 +63,23 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
62 63
}
63 64
64 65
@Override
65
- protected void addColumnsToLeaderboardTable(CellTable<StrippedLeaderboardDTO> leaderboardTable) {
66
+ protected void addColumnsToLeaderboardTableAndSetSelectionModel(CellTable<StrippedLeaderboardDTO> leaderboardTable, AdminConsoleTableResources tableResources) {
66 67
ListHandler<StrippedLeaderboardDTO> leaderboardColumnListHandler = new ListHandler<StrippedLeaderboardDTO>(
67 68
leaderboardList.getList());
68
-
69
+ SelectionCheckboxColumn<StrippedLeaderboardDTO> selectionCheckboxColumn = createSortableSelectionCheckboxColumn(
70
+ leaderboardTable, tableResources, leaderboardColumnListHandler);
69 71
TextColumn<StrippedLeaderboardDTO> leaderboardNameColumn = new TextColumn<StrippedLeaderboardDTO>() {
70 72
@Override
71 73
public String getValue(StrippedLeaderboardDTO leaderboard) {
72 74
return leaderboard.name;
73 75
}
74 76
};
75
-
76 77
TextColumn<StrippedLeaderboardDTO> leaderboardDisplayNameColumn = new TextColumn<StrippedLeaderboardDTO>() {
77 78
@Override
78 79
public String getValue(StrippedLeaderboardDTO leaderboard) {
79 80
return leaderboard.getDisplayName() != null ? leaderboard.getDisplayName() : "";
80 81
}
81 82
};
82
-
83 83
ImagesBarColumn<StrippedLeaderboardDTO, RaceLogTrackingEventManagementImagesBarCell> leaderboardActionColumn =
84 84
new ImagesBarColumn<StrippedLeaderboardDTO, RaceLogTrackingEventManagementImagesBarCell>(
85 85
new RaceLogTrackingEventManagementImagesBarCell(stringMessages));
... ...
@@ -91,11 +91,13 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
91 91
}
92 92
}
93 93
});
94
-
94
+ leaderboardTable.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader());
95 95
leaderboardTable.addColumn(leaderboardNameColumn, stringMessages.name());
96 96
leaderboardTable.addColumn(leaderboardDisplayNameColumn, stringMessages.displayName());
97 97
leaderboardTable.addColumn(leaderboardActionColumn, stringMessages.actions());
98 98
leaderboardTable.addColumnSortHandler(leaderboardColumnListHandler);
99
+ leaderboardTable.setSelectionModel(new MultiSelectionModel<StrippedLeaderboardDTO>());
100
+ leaderboardTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager());
99 101
}
100 102
101 103
private RaceLogTrackingState getTrackingState(RaceColumnDTOAndFleetDTOWithNameBasedEquality race) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
... ...
@@ -285,8 +285,9 @@ public interface SailingService extends RemoteService {
285 285
String logoImageURL, Iterable<String> imageURLs, Iterable<String> videoURLs,
286 286
Iterable<String> sponsorImageURLs) throws Exception;
287 287
288
- EventDTO createEvent(String eventName, Date startDate, Date endDate, String description, boolean isPublic,
289
- List<String> courseAreaNames, Iterable<String> imageURLs, Iterable<String> videoURLs) throws Exception;
288
+ EventDTO createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue,
289
+ boolean isPublic, List<String> courseAreaNames, Iterable<String> imageURLs,
290
+ Iterable<String> videoURLs, Iterable<String> sponsorImageURLs, String logoImageURL, String officialWebsiteURL) throws Exception;
290 291
291 292
void removeEvent(UUID eventId);
292 293
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
... ...
@@ -321,6 +321,11 @@ public interface SailingServiceAsync {
321 321
void setWindSourcesToExclude(RegattaAndRaceIdentifier raceIdentifier, Iterable<WindSource> windSourcesToExclude,
322 322
AsyncCallback<Void> callback);
323 323
324
+ /**
325
+ * @param date
326
+ * use <code>null</code> to indicate "live" in which case the server live time stamp for the race
327
+ * identified by <code>raceIdentifier</code> will be used, considering that race's delay.
328
+ */
324 329
void getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map<String, Date> fromPerCompetitorIdAsString,
325 330
Map<String, Date> toPerCompetitorIdAsString, boolean extrapolate, AsyncCallback<CompactRaceMapDataDTO> callback);
326 331
... ...
@@ -347,9 +352,9 @@ public interface SailingServiceAsync {
347 352
348 353
void removeEvents(Collection<UUID> eventIds, AsyncCallback<Void> asyncCallback);
349 354
350
- void createEvent(String eventName, Date startDate, Date endDate, String description, boolean isPublic,
355
+ void createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, boolean isPublic,
351 356
List<String> courseAreaNames, Iterable<String> imageURLs, Iterable<String> videoURLs,
352
- AsyncCallback<EventDTO> callback);
357
+ Iterable<String> sponsorImageURLs, String logoImageURL, String officialWebsiteURL, AsyncCallback<EventDTO> callback);
353 358
354 359
void updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate,
355 360
VenueDTO venue, boolean isPublic, Iterable<UUID> leaderboardGroupIds, String officialWebsiteURL,
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
... ...
@@ -387,6 +387,7 @@ public interface StringMessages extends Messages {
387 387
String scoringSchemeWinnerGetsFive();
388 388
String scoringSchemeWinnerGetsFiveIgnoringRaceCount();
389 389
String scoringSchemeWinnerGetsSix();
390
+ String scoringSchemeWinnerGetsSixIgnoringRaceCount();
390 391
String scoringSystem();
391 392
String createFlexibleLeaderboard();
392 393
String createRegattaLeaderboard();
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
... ...
@@ -16,7 +16,7 @@ race=Race
16 16
races=Races
17 17
tracked=tracked
18 18
time=Time
19
-timeTooltip=Time the competitor sailed on this leg. For the first leg this is counted from the start, not when the competitor passed the start line.
19
+timeTooltip=Time the competitor sailed on this leg. For the first leg this is counted from when the competitor passed the start line.
20 20
playSpeed=Play speed
21 21
playSpeedHelp=Use "1" for regular play-back speed, "2" for double-speed playback and so on
22 22
playModeLive=Live
... ...
@@ -411,6 +411,7 @@ scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero
411 411
scoringSchemeWinnerGetsFive=High Point System, Winner Gets 5 Points
412 412
scoringSchemeWinnerGetsFiveIgnoringRaceCount=High Point System, Winner Gets 5 Points, Ignoring Race Count
413 413
scoringSchemeWinnerGetsSix=High Point System, Winner Gets 6 Points
414
+scoringSchemeWinnerGetsSixIgnoringRaceCount=High Point System, Winner Gets 6 Points, Ignoring Race Count
414 415
scoringSystem=Scoring system
415 416
createFlexibleLeaderboard=Create flexible leaderboard
416 417
createRegattaLeaderboard=Create regatta leaderboard
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
... ...
@@ -16,7 +16,7 @@ race=Wettfahrt
16 16
races=Wettfahrten
17 17
tracked=getracked
18 18
time=Zeit
19
-timeTooltip=Zeit, die der Teilnehmer auf diesem Schenkel gesegelt ist.
19
+timeTooltip=Zeit, die der Teilnehmer auf diesem Schenkel gesegelt ist. Beim ersten Schenkel zählt die Zeit ab Überquerung der Startlinie.
20 20
playSpeed=Abspielgeschwindigkeit
21 21
playSpeedHelp="1" für normale Abspielgeschwindigkeit, "2" für doppelte Geschwindigkeit usw.
22 22
playModeLive=Live
... ...
@@ -412,6 +412,7 @@ scoringSchemeLowPointWinnerGetsZero=Low Point System, Gewinner erhält 0 Punkte
412 412
scoringSchemeWinnerGetsFive=High Point System, Gewinner erhält 5 Punkte
413 413
scoringSchemeWinnerGetsFiveIgnoringRaceCount=High Point System, Gewinner erhält 5 Punkte, Anzahl Wettfahrten egal
414 414
scoringSchemeWinnerGetsSix=High Point System, Gewinner erhält 6 Punkte
415
+scoringSchemeWinnerGetsSixIgnoringRaceCount=High Point System, Gewinner erhält 6 Punkte, Anzahl Wettfahrten egal
415 416
scoringSystem=Bepunktungssystem
416 417
createFlexibleLeaderboard=Flexible Rangliste anlegen
417 418
createRegattaLeaderboard=Rangliste für Regatta anlegen
... ...
@@ -1047,7 +1048,7 @@ currentOrAverageSpeedOverGroundInKnots=FüG (\u2205 nach Abschluss)
1047 1048
scoringSchemeHighPointFirstGetsTenOrEight=High Point, Gewinner bekommt 10 Punkte (oder 8)
1048 1049
videoComponentShortName=Video
1049 1050
competitorSearchFilter=Teilnehmer-Suchfilter
1050
-searchCompetitorsBySailNumberOrName=Suche
1051
+searchCompetitorsBySailNumberOrName=Suchen
1051 1052
goToEventOverview=Zur Veranstaltungsübersicht
1052 1053
goToOverviewAndSeeLeaderboard=Zur Ergebnisübersicht mit allen Wettfahrten der Regatta
1053 1054
noSuchEvent=Veranstaltung nicht gefunden
... ...
@@ -1060,4 +1061,4 @@ showVideoPopup=Video zeigen
1060 1061
hideVideoPopup=Video verstecken
1061 1062
manageMedia=Audio/Video verwalten
1062 1063
manageMediaTooltip=Audio- und Video-Clips verwalten, Zeiten synchronisieren
1063
-showAll=Alle zeigen
... ...
\ No newline at end of file
0
+showAll=Alle zeigen
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
... ...
@@ -17,7 +17,8 @@ race=Гонка
17 17
races=Гонки
18 18
tracked=Отслежена
19 19
time=Время
20
-timeTooltip=Учет времени участника, выступавшего на этом этапе. На первом этапе учет времени ведется с начала гонки, а не при пересечении участником линии старта.
20
+timeTooltip=Time the competitor sailed on this leg. For the first leg this is counted from when the competitor passed the start line.
21
+# timeTooltip=Учет времени участника, выступавшего на этом этапе. На первом этапе учет времени ведется с начала гонки, а не при пересечении участником линии старта.
21 22
playSpeed=Скорость воспроизведения
22 23
playSpeedHelp=Используйте "1" для обычной скорости воспроизведения, "2" для удвоенной скорости воспроизведения и т. д.
23 24
playModeLive=В прямом эфире
... ...
@@ -402,6 +403,7 @@ scoringSchemeLowPointWinnerGetsZero=Низкобальная система по
402 403
scoringSchemeWinnerGetsFive=Высокобальная система подсчета, победитель получает 5 очков
403 404
scoringSchemeWinnerGetsFiveIgnoringRaceCount=Высокобальная система подсчета, победитель получает 5 очков, Ignoring Race Count
404 405
scoringSchemeWinnerGetsSix=Высокобальная система подсчета, победитель получает 6 очков
406
+scoringSchemeWinnerGetsSixIgnoringRaceCount=Высокобальная система подсчета, победитель получает 6 очков, Ignoring Race Count
405 407
scoringSystem=Система подсчета очков
406 408
createFlexibleLeaderboard=Создание адаптивной таблицы лидеров
407 409
createRegattaLeaderboard=Создание таблицы лидеров регаты
... ...
@@ -1002,7 +1004,7 @@ currentOrAverageSpeedOverGroundInKnots=SOG (\u2205 at end)
1002 1004
scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8)
1003 1005
videoComponentShortName=Video
1004 1006
competitorSearchFilter=Competitor Search Filter
1005
-searchCompetitorsBySailNumberOrName=Search by Sail# / Name
1007
+searchCompetitorsBySailNumberOrName=Search
1006 1008
goToEventOverview=Go to the event overview
1007 1009
goToOverviewAndSeeLeaderboard=Go to the overview and see all Races in one Leaderboard
1008 1010
noSuchEvent=No such event
... ...
@@ -1015,4 +1017,4 @@ showVideoPopup=Show Video
1015 1017
hideVideoPopup=Hide Video
1016 1018
manageMedia=Manage Media
1017 1019
manageMediaTooltip=Configure audio and video clips, synchronize times
1018
-showAll=Show all
... ...
\ No newline at end of file
0
+showAll=Show all
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java
... ...
@@ -21,6 +21,8 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
21 21
import com.google.gwt.user.client.ui.SimplePanel;
22 22
import com.google.gwt.user.client.ui.Widget;
23 23
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
24
+import com.sap.sailing.domain.common.TimePoint;
25
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
24 26
import com.sap.sailing.domain.common.media.MediaTrack;
25 27
import com.sap.sailing.domain.common.media.MediaTrack.MediaType;
26 28
import com.sap.sailing.domain.common.media.MediaTrack.Status;
... ...
@@ -180,7 +182,8 @@ public class MediaPlayerManagerComponent implements Component<Void>, PlayStateLi
180 182
@Override
181 183
public void playStateChanged(PlayStates playState, PlayModes playMode) {
182 184
this.currentPlayState = playState;
183
- if (PlayModes.Replay.equals(playMode)) {
185
+ switch (playMode) {
186
+ case Replay:
184 187
switch (this.currentPlayState) {
185 188
case Playing:
186 189
startPlaying();
... ...
@@ -190,8 +193,13 @@ public class MediaPlayerManagerComponent implements Component<Void>, PlayStateLi
190 193
default:
191 194
break;
192 195
}
193
- } else {
196
+ break;
197
+ case Live:
194 198
// TODO: Live mode not supported, yet.
199
+ startPlaying();
200
+ break;
201
+ default:
202
+ break;
195 203
}
196 204
}
197 205
... ...
@@ -445,8 +453,8 @@ public class MediaPlayerManagerComponent implements Component<Void>, PlayStateLi
445 453
notifyStateChange();
446 454
}
447 455
448
- private long getRaceStartTime() {
449
- return raceTimesInfoProvider.getRaceTimesInfo(raceIdentifier).startOfRace.getTime();
456
+ private TimePoint getRaceStartTime() {
457
+ return new MillisecondsTimePoint(raceTimesInfoProvider.getRaceTimesInfo(raceIdentifier).startOfRace);
450 458
}
451 459
452 460
@Override
... ...
@@ -526,7 +534,7 @@ public class MediaPlayerManagerComponent implements Component<Void>, PlayStateLi
526 534
527 535
@Override
528 536
public void addMediaTrack() {
529
- Date defaultStartTime = new Date(getRaceStartTime());
537
+ TimePoint defaultStartTime = getRaceStartTime();
530 538
NewMediaDialog dialog = new NewMediaDialog(defaultStartTime, MediaPlayerManagerComponent.this.stringMessages, new DialogCallback<MediaTrack>() {
531 539
532 540
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaSynchControl.java
... ...
@@ -38,7 +38,7 @@ public class MediaSynchControl implements EditFlag {
38 38
this.mediaSynchAdapter = mediaSynchAdapter;
39 39
this.errorReporter = errorReporter;
40 40
MediaTrack videoTrack = this.mediaSynchAdapter.getMediaTrack();
41
- backupVideoTrack = new MediaTrack(null, videoTrack.title, videoTrack.url, videoTrack.startTime, videoTrack.durationInMillis, videoTrack.mimeType);
41
+ backupVideoTrack = new MediaTrack(null, videoTrack.title, videoTrack.url, videoTrack.startTime, videoTrack.duration, videoTrack.mimeType);
42 42
mainPanel = new FlowPanel();
43 43
fineTuningPanel = new FlowPanel();
44 44
fineTuningPanel.addStyleName("finetuning-panel");
... ...
@@ -144,7 +144,7 @@ public class MediaSynchControl implements EditFlag {
144 144
// For now, only start time can be changed.
145 145
// getMediaTrack().title = backupVideoTrack.title;
146 146
// getMediaTrack().url = backupVideoTrack.url;
147
-// getMediaTrack().durationInMillis = backupVideoTrack.durationInMillis;
147
+// getMediaTrack().duration = backupVideoTrack.duration;
148 148
}
149 149
150 150
private void save() {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java
... ...
@@ -1,7 +1,5 @@
1 1
package com.sap.sailing.gwt.ui.client.media;
2 2
3
-import java.util.Date;
4
-
5 3
import com.google.gwt.dom.client.MediaElement;
6 4
import com.google.gwt.dom.client.Style;
7 5
import com.google.gwt.event.dom.client.ChangeEvent;
... ...
@@ -13,6 +11,8 @@ import com.google.gwt.user.client.ui.Label;
13 11
import com.google.gwt.user.client.ui.TextBox;
14 12
import com.google.gwt.user.client.ui.VerticalPanel;
15 13
import com.google.gwt.user.client.ui.Widget;
14
+import com.sap.sailing.domain.common.TimePoint;
15
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
16 16
import com.sap.sailing.domain.common.media.MediaTrack;
17 17
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
18 18
import com.sap.sailing.gwt.ui.client.StringMessages;
... ...
@@ -46,11 +46,11 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
46 46
47 47
private Label durationLabel;
48 48
49
- private Date defaultStartTime;
49
+ private TimePoint defaultStartTime;
50 50
51 51
private Label infoLabelLabel;
52 52
53
- public NewMediaDialog(Date defaultStartTime, StringMessages stringMessages, DialogCallback<MediaTrack> dialogCallback) {
53
+ public NewMediaDialog(TimePoint defaultStartTime, StringMessages stringMessages, DialogCallback<MediaTrack> dialogCallback) {
54 54
super(stringMessages.addMediaTrack(), "", stringMessages.ok(), stringMessages.cancel(), MEDIA_TRACK_VALIDATOR, dialogCallback);
55 55
this.defaultStartTime = defaultStartTime;
56 56
this.stringMessages = stringMessages;
... ...
@@ -84,7 +84,7 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
84 84
85 85
public void loadedmetadata(MediaElement mediaElement) {
86 86
mediaTrack.startTime = this.defaultStartTime;
87
- mediaTrack.durationInMillis = (int) Math.round(mediaElement.getDuration() * 1000);
87
+ mediaTrack.duration = new MillisecondsDurationImpl((long) Math.round(mediaElement.getDuration() * 1000));
88 88
refreshUI();
89 89
}
90 90
... ...
@@ -203,9 +203,9 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
203 203
setUiEnabled(true);
204 204
mediaTrack.title = title;
205 205
try {
206
- mediaTrack.durationInMillis = (int) (1000 * Double.valueOf(durationInSeconds));
206
+ mediaTrack.duration = new MillisecondsDurationImpl((long) Math.round(1000 * Double.valueOf(durationInSeconds)));
207 207
} catch (NumberFormatException ex) {
208
- mediaTrack.durationInMillis = 0;
208
+ mediaTrack.duration = null;
209 209
}
210 210
mediaTrack.startTime = this.defaultStartTime;
211 211
refreshUI();
... ...
@@ -220,9 +220,9 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
220 220
infoLabelLabel.setText(stringMessages.mimeType() + ":");
221 221
infoLabel.setText(mediaTrack.typeToString());
222 222
}
223
- String startTimeText = mediaTrack.startTime == null ? "undefined" : TimeFormatUtil.DATETIME_FORMAT.format(mediaTrack.startTime);
223
+ String startTimeText = mediaTrack.startTime == null ? "undefined" : TimeFormatUtil.DATETIME_FORMAT.format(mediaTrack.startTime.asDate());
224 224
startTimeLabel.setText(startTimeText);
225
- durationLabel.setText(TimeFormatUtil.milliSecondsToHrsMinSec(mediaTrack.durationInMillis));
225
+ durationLabel.setText(TimeFormatUtil.durationToHrsMinSec(mediaTrack.duration));
226 226
}
227 227
228 228
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/TimeFormatUtil.java
... ...
@@ -3,6 +3,8 @@ package com.sap.sailing.gwt.ui.client.media;
3 3
import com.google.gwt.i18n.client.DateTimeFormat;
4 4
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
5 5
import com.google.gwt.i18n.client.NumberFormat;
6
+import com.sap.sailing.domain.common.Duration;
7
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
6 8
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
7 9
8 10
public class TimeFormatUtil {
... ...
@@ -16,9 +18,9 @@ public class TimeFormatUtil {
16 18
private static NumberFormat zeroPaddingNumberFormat_Min = NumberFormatterFactory.getDecimalFormat(2, 0);
17 19
private static NumberFormat zeroPaddingNumberFormat_Sec = NumberFormatterFactory.getDecimalFormat(2, 3);
18 20
19
- public static int hrsMinSecToMilliSeconds(String hrsMinSec) {
21
+ public static Duration hrsMinSecToMilliSeconds(String hrsMinSec) {
20 22
String[] segements = hrsMinSec.split(":");
21
- int milliseconds = 0;
23
+ long milliseconds = 0;
22 24
boolean isNegative = false;
23 25
switch (segements.length) {
24 26
case 3:
... ...
@@ -38,34 +40,39 @@ public class TimeFormatUtil {
38 40
milliseconds = milliseconds + Math.round(seconds * 1000);
39 41
}
40 42
if (isNegative) {
41
- return -milliseconds;
43
+ return new MillisecondsDurationImpl(-milliseconds);
42 44
} else {
43
- return milliseconds;
45
+ return new MillisecondsDurationImpl(milliseconds);
44 46
}
45 47
}
46 48
47
- public static String milliSecondsToHrsMinSec(long milliseconds) {
48
- int signum = Long.signum(milliseconds);
49
- milliseconds = Math.abs(milliseconds);
50
-
51
- StringBuilder result = new StringBuilder();
52
-
53
- long hours = (milliseconds / MILLISECONDS_PER_HOUR);
54
- if (hours > 0) {
55
- result.append(String.valueOf(signum * hours) + ':');
56
- signum = 1;
57
- }
58
- milliseconds = Math.abs(milliseconds);
59
- long rest = (milliseconds % MILLISECONDS_PER_HOUR);
60
- long minutes = (rest / MILLISECONDS_PER_MINUTE);
61
- if (minutes > 0 || result.length() > 0) {
62
- result.append(zeroPaddingNumberFormat_Min.format(signum * minutes) + ':');
63
- signum = 1;
49
+ public static String durationToHrsMinSec(Duration duration) {
50
+ if (duration != null) {
51
+ long milliseconds = duration.asMillis();
52
+ int signum = Long.signum(milliseconds);
53
+ milliseconds = Math.abs(milliseconds);
54
+
55
+ StringBuilder result = new StringBuilder();
56
+
57
+ long hours = (milliseconds / MILLISECONDS_PER_HOUR);
58
+ if (hours > 0) {
59
+ result.append(String.valueOf(signum * hours) + ':');
60
+ signum = 1;
61
+ }
62
+ milliseconds = Math.abs(milliseconds);
63
+ long rest = (milliseconds % MILLISECONDS_PER_HOUR);
64
+ long minutes = (rest / MILLISECONDS_PER_MINUTE);
65
+ if (minutes > 0 || result.length() > 0) {
66
+ result.append(zeroPaddingNumberFormat_Min.format(signum * minutes) + ':');
67
+ signum = 1;
68
+ }
69
+ rest = (rest % MILLISECONDS_PER_MINUTE);
70
+ double seconds = rest / 1000d;
71
+ result.append(zeroPaddingNumberFormat_Sec.format(signum * seconds));
72
+ return result.toString();
73
+ } else {
74
+ return "";
64 75
}
65
- rest = (rest % MILLISECONDS_PER_MINUTE);
66
- double seconds = rest / 1000d;
67
- result.append(zeroPaddingNumberFormat_Sec.format(signum * seconds));
68
- return result.toString();
69 76
}
70 77
71 78
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoHtmlPlayer.java
... ...
@@ -1,10 +1,10 @@
1 1
package com.sap.sailing.gwt.ui.client.media;
2 2
3
-import java.util.Date;
4
-
5 3
import com.google.gwt.media.client.MediaBase;
6 4
import com.google.gwt.media.client.Video;
7 5
import com.google.gwt.user.client.ui.Widget;
6
+import com.sap.sailing.domain.common.TimePoint;
7
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
8 8
import com.sap.sailing.domain.common.media.MediaTrack;
9 9
import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer;
10 10
import com.sap.sailing.gwt.ui.client.media.shared.WithWidget;
... ...
@@ -12,14 +12,14 @@ import com.sap.sse.gwt.client.player.Timer;
12 12
13 13
public class VideoHtmlPlayer extends AbstractHtmlMediaPlayer implements VideoSynchPlayer, MediaSynchAdapter, WithWidget {
14 14
15
- private final long raceStartTimeMillis;
15
+ private final TimePoint raceStartTime;
16 16
private final Timer raceTimer;
17 17
private EditFlag editFlag;
18 18
19
- public VideoHtmlPlayer(final MediaTrack videoTrack, long raceStartTimeMillis, boolean showSynchControls, Timer raceTimer) {
19
+ public VideoHtmlPlayer(final MediaTrack videoTrack, TimePoint raceStartTime, boolean showSynchControls, Timer raceTimer) {
20 20
super(videoTrack);
21 21
this.raceTimer = raceTimer;
22
- this.raceStartTimeMillis = raceStartTimeMillis;
22
+ this.raceStartTime = raceStartTime;
23 23
}
24 24
25 25
@Override
... ...
@@ -29,18 +29,18 @@ public class VideoHtmlPlayer extends AbstractHtmlMediaPlayer implements VideoSyn
29 29
30 30
@Override
31 31
public long getOffset() {
32
- return getMediaTrack().startTime.getTime() - raceStartTimeMillis;
32
+ return getMediaTrack().startTime.asMillis() - raceStartTime.asMillis();
33 33
}
34 34
35 35
@Override
36 36
public void changeOffsetBy(long delta) {
37
- getMediaTrack().startTime = new Date(getMediaTrack().startTime.getTime() + delta);
37
+ getMediaTrack().startTime = getMediaTrack().startTime.plus(delta);
38 38
forceAlign();
39 39
}
40 40
41 41
@Override
42 42
public void updateOffset() {
43
- getMediaTrack().startTime = new Date(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis());
43
+ getMediaTrack().startTime = new MillisecondsTimePoint(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis());
44 44
}
45 45
46 46
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoYoutubePlayer.java
... ...
@@ -1,7 +1,6 @@
1 1
package com.sap.sailing.gwt.ui.client.media;
2 2
3 3
import java.util.ArrayList;
4
-import java.util.Date;
5 4
import java.util.List;
6 5
7 6
import com.google.gwt.event.logical.shared.AttachEvent;
... ...
@@ -9,6 +8,8 @@ import com.google.gwt.event.logical.shared.AttachEvent.Handler;
9 8
import com.google.gwt.user.client.ui.Panel;
10 9
import com.google.gwt.user.client.ui.SimplePanel;
11 10
import com.google.gwt.user.client.ui.Widget;
11
+import com.sap.sailing.domain.common.TimePoint;
12
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
12 13
import com.sap.sailing.domain.common.media.MediaTrack;
13 14
import com.sap.sailing.gwt.ui.client.media.shared.AbstractMediaPlayer;
14 15
import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer;
... ...
@@ -25,7 +26,7 @@ public class VideoYoutubePlayer extends AbstractMediaPlayer implements VideoSync
25 26
26 27
private static int videoCounter;
27 28
28
- private final long raceStartTimeMillis;
29
+ private final TimePoint raceStartTime;
29 30
30 31
private final Timer raceTimer;
31 32
... ...
@@ -44,10 +45,10 @@ public class VideoYoutubePlayer extends AbstractMediaPlayer implements VideoSync
44 45
private final List<DeferredAction> deferredActions = new ArrayList<DeferredAction>();
45 46
46 47
47
- public VideoYoutubePlayer(final MediaTrack videoTrack, long raceStartTimeMillis, final boolean showControls, Timer raceTimer) {
48
+ public VideoYoutubePlayer(final MediaTrack videoTrack, TimePoint raceStartTime, final boolean showControls, Timer raceTimer) {
48 49
super(videoTrack);
49 50
this.raceTimer = raceTimer;
50
- this.raceStartTimeMillis = raceStartTimeMillis;
51
+ this.raceStartTime = raceStartTime;
51 52
52 53
this.videoContainer = new SimplePanel();
53 54
final String videoContainerId = "videoContainer-" + videoTrack.url + ++videoCounter;
... ...
@@ -85,18 +86,18 @@ public class VideoYoutubePlayer extends AbstractMediaPlayer implements VideoSync
85 86
86 87
@Override
87 88
public long getOffset() {
88
- return getMediaTrack().startTime.getTime() - raceStartTimeMillis;
89
+ return getMediaTrack().startTime.asMillis() - raceStartTime.asMillis();
89 90
}
90 91
91 92
@Override
92 93
public void changeOffsetBy(long delta) {
93
- getMediaTrack().startTime = new Date(getMediaTrack().startTime.getTime() + delta);
94
+ getMediaTrack().startTime = getMediaTrack().startTime.plus(delta);
94 95
forceAlign();
95 96
}
96 97
97 98
@Override
98 99
public void updateOffset() {
99
- getMediaTrack().startTime = new Date(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis());
100
+ getMediaTrack().startTime = new MillisecondsTimePoint(raceTimer.getTime().getTime() - getCurrentMediaTimeMillis());
100 101
}
101 102
102 103
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/shared/AbstractMediaPlayer.java
... ...
@@ -24,7 +24,7 @@ public abstract class AbstractMediaPlayer implements MediaPlayer {
24 24
}
25 25
26 26
public void forceAlign() {
27
- forceAlign(mediaTrack.startTime.getTime());
27
+ forceAlign(mediaTrack.startTime.asMillis());
28 28
}
29 29
30 30
public void raceTimeChanged(Date raceTime) {
... ...
@@ -39,15 +39,15 @@ public abstract class AbstractMediaPlayer implements MediaPlayer {
39 39
40 40
@Override
41 41
public boolean isCoveringCurrentRaceTime() {
42
- double mediaTime = (raceTimeInMillis - mediaTrack.startTime.getTime()) / 1000d;
42
+ double mediaTime = (raceTimeInMillis - mediaTrack.startTime.asMillis()) / 1000d;
43 43
return (mediaTime >= 0) && (mediaTime <= getDuration());
44 44
}
45 45
46 46
protected void alignTime() {
47
- long mediaStartTimeInMillis = mediaTrack.startTime.getTime();
47
+ long mediaStartTimeInMillis = mediaTrack.startTime.asMillis();
48 48
long mediaTimeInMillis = mediaStartTimeInMillis + getCurrentMediaTimeMillis();
49 49
long mediaLaggingBehindRaceInMillis = raceTimeInMillis - mediaTimeInMillis;
50
- if (Math.abs(mediaLaggingBehindRaceInMillis) > TOLERATED_LAG_IN_MILLISECONDS) {
50
+ if (mediaLaggingBehindRaceInMillis > TOLERATED_LAG_IN_MILLISECONDS) {
51 51
forceAlign(mediaStartTimeInMillis);
52 52
}
53 53
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java
... ...
@@ -17,7 +17,7 @@ import com.sap.sailing.gwt.ui.shared.racemap.MarkVectorGraphics;
17 17
import com.sap.sse.common.Util;
18 18
19 19
/**
20
- * A google map overlay based on a HTML5 canvas for drawing course marks (images) and the buoy zone if the mark is a buoy.
20
+ * A google map overlay based on a HTML5 canvas for drawing course marks (canvases) and the buoy zone if the mark is a buoy.
21 21
*/
22 22
public class CourseMarkOverlay extends CanvasOverlayV3 {
23 23
... ...
@@ -37,6 +37,12 @@ public class CourseMarkOverlay extends CanvasOverlayV3 {
37 37
private final MarkVectorGraphics markVectorGraphics;
38 38
39 39
private Map<Integer, Util.Pair<Double, Size>> markScaleAndSizePerZoomCache;
40
+
41
+ private Double lastWidth;
42
+ private Double lastHeight;
43
+ private Double lastScaleFactor;
44
+ private Boolean lastShowBuoyZone;
45
+ private Double lastBuoyZoneRadiusInMeter;
40 46
41 47
public CourseMarkOverlay(MapWidget map, int zIndex, MarkDTO markDTO) {
42 48
super(map, zIndex);
... ...
@@ -44,68 +50,78 @@ public class CourseMarkOverlay extends CanvasOverlayV3 {
44 50
this.position = markDTO.position;
45 51
this.buoyZoneRadiusInMeter = 0.0;
46 52
this.showBuoyZone = false;
47
-
48 53
markVectorGraphics = new MarkVectorGraphics(markDTO.type, markDTO.color, markDTO.shape, markDTO.pattern);
49 54
markScaleAndSizePerZoomCache = new HashMap<Integer, Util.Pair<Double,Size>>();
50
-
51 55
setCanvasSize(50, 50);
52 56
}
53 57
54
-
55 58
@Override
56 59
protected void draw() {
57 60
if (mapProjection != null && mark != null && position != null) {
58 61
int zoom = map.getZoom();
59
-
60 62
Util.Pair<Double, Size> markScaleAndSize = markScaleAndSizePerZoomCache.get(zoom);
61
- if(markScaleAndSize == null) {
63
+ if (markScaleAndSize == null) {
62 64
markScaleAndSize = getMarkScaleAndSize(position);
63 65
markScaleAndSizePerZoomCache.put(zoom, markScaleAndSize);
64 66
}
65 67
double markSizeScaleFactor = markScaleAndSize.getA();
66
-
67 68
getCanvas().setTitle(getTitle());
68
-
69 69
LatLng latLngPosition = LatLng.newInstance(position.latDeg, position.lngDeg);
70
-
71 70
// calculate canvas size
72 71
double canvasWidth = markScaleAndSize.getB().getWidth();
73 72
double canvasHeight = markScaleAndSize.getB().getHeight();
74 73
double buoyZoneRadiusInPixel = -1;
75
- if(showBuoyZone && mark.type == MarkType.BUOY) {
76
- buoyZoneRadiusInPixel = calculateRadiusOfBoundingBox(mapProjection, latLngPosition, buoyZoneRadiusInMeter);
77
- if(buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) {
78
- canvasWidth = (buoyZoneRadiusInPixel + 1)* 2;
74
+ if (showBuoyZone && mark.type == MarkType.BUOY) {
75
+ buoyZoneRadiusInPixel = calculateRadiusOfBoundingBox(mapProjection, latLngPosition,
76
+ buoyZoneRadiusInMeter);
77
+ if (buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) {
78
+ canvasWidth = (buoyZoneRadiusInPixel + 1) * 2;
79 79
canvasHeight = (buoyZoneRadiusInPixel + 1) * 2;
80 80
}
81 81
}
82
- setCanvasSize((int) canvasWidth, (int) canvasHeight);
83
-
84
- Context2d context2d = getCanvas().getContext2d();
85
-
86
- // draw the course mark
87
- markVectorGraphics.drawMarkToCanvas(context2d, showBuoyZone, canvasWidth, canvasHeight, markSizeScaleFactor);
88
-
82
+ if (needToDraw(showBuoyZone, buoyZoneRadiusInMeter, canvasWidth, canvasHeight, markSizeScaleFactor)) {
83
+ setCanvasSize((int) canvasWidth, (int) canvasHeight);
84
+ Context2d context2d = getCanvas().getContext2d();
85
+ // draw the course mark
86
+ markVectorGraphics.drawMarkToCanvas(context2d, showBuoyZone, canvasWidth, canvasHeight, markSizeScaleFactor);
87
+ // draw the buoy zone
88
+ if (showBuoyZone && mark.type == MarkType.BUOY && buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) {
89
+ CssColor grayTransparentColor = CssColor.make("rgba(50,90,135,0.75)");
90
+ // this translation is important for drawing lines with a real line width of 1 pixel
91
+ context2d.setStrokeStyle(grayTransparentColor);
92
+ context2d.setLineWidth(1.0);
93
+ context2d.beginPath();
94
+ context2d.arc(buoyZoneRadiusInPixel + 1, buoyZoneRadiusInPixel + 1, buoyZoneRadiusInPixel, 0,
95
+ Math.PI * 2, true);
96
+ context2d.closePath();
97
+ context2d.stroke();
98
+ }
99
+ lastBuoyZoneRadiusInMeter = buoyZoneRadiusInMeter;
100
+ lastScaleFactor = markSizeScaleFactor;
101
+ lastShowBuoyZone = showBuoyZone;
102
+ lastWidth = canvasWidth;
103
+ lastHeight = canvasHeight;
104
+ }
89 105
Point buoyPositionInPx = mapProjection.fromLatLngToDivPixel(latLngPosition);
90
-
91
- // draw the buoy zone
92
- if(showBuoyZone && mark.type == MarkType.BUOY && buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) {
93
- CssColor grayTransparentColor = CssColor.make("rgba(50,90,135,0.75)");
94
-
95
- // this translation is important for drawing lines with a real line width of 1 pixel
96
- context2d.setStrokeStyle(grayTransparentColor);
97
- context2d.setLineWidth(1.0);
98
- context2d.beginPath();
99
- context2d.arc(buoyZoneRadiusInPixel+1, buoyZoneRadiusInPixel+1, buoyZoneRadiusInPixel, 0, Math.PI*2, true);
100
- context2d.closePath();
101
- context2d.stroke();
102
-
106
+ if (showBuoyZone && mark.type == MarkType.BUOY && buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) {
103 107
setCanvasPosition(buoyPositionInPx.getX() - buoyZoneRadiusInPixel, buoyPositionInPx.getY() - buoyZoneRadiusInPixel);
104 108
} else {
105 109
setCanvasPosition(buoyPositionInPx.getX() - canvasWidth / 2.0, buoyPositionInPx.getY() - canvasHeight / 2.0);
106 110
}
107 111
}
108 112
}
113
+
114
+ /**
115
+ * Compares the drawing parameters to {@link #lastLegType} and the other <code>last...</code>. If anything has
116
+ * changed, the result is <code>true</code>.
117
+ */
118
+ private boolean needToDraw(boolean showBuoyZone, double buoyZoneRadiusInMeters, double width, double height, double scaleFactor) {
119
+ return lastShowBuoyZone == null || lastShowBuoyZone != showBuoyZone ||
120
+ lastBuoyZoneRadiusInMeter == null || lastBuoyZoneRadiusInMeter != buoyZoneRadiusInMeters ||
121
+ lastScaleFactor == null || lastScaleFactor != scaleFactor ||
122
+ lastWidth == null || lastWidth != width ||
123
+ lastHeight == null || lastHeight != height;
124
+ }
109 125
110 126
public Util.Pair<Double, Size> getMarkScaleAndSize(PositionDTO markPosition) {
111 127
double minMarkHeight = 20;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
... ...
@@ -124,6 +124,7 @@ import com.sap.sse.common.filter.FilterSet;
124 124
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
125 125
import com.sap.sse.gwt.client.player.TimeListener;
126 126
import com.sap.sse.gwt.client.player.Timer;
127
+import com.sap.sse.gwt.client.player.Timer.PlayModes;
127 128
import com.sap.sse.gwt.client.player.Timer.PlayStates;
128 129
129 130
public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener,
... ...
@@ -469,7 +470,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
469 470
470 471
LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey);
471 472
}
472
-
473
+
473 474
/**
474 475
* Creates a header panel where additional information can be displayed by using
475 476
* {@link #getLeftHeaderPanel()} or {@link #getRightHeaderPanel()}.
... ...
@@ -498,7 +499,6 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
498 499
// controls at RIGHT would not get the correct top setting
499 500
map.setControls(ControlPosition.TOP_RIGHT, headerPanel);
500 501
}
501
-
502 502
private void createSettingsButton(MapWidget map) {
503 503
final Component<RaceMapSettings> component = this;
504 504
Button settingsButton = new Button();
... ...
@@ -512,7 +512,6 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
512 512
});
513 513
map.setControls(ControlPosition.RIGHT_TOP, settingsButton);
514 514
}
515
-
516 515
517 516
private void removeTransitions() {
518 517
// remove the canvas animations for boats
... ...
@@ -561,6 +560,16 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
561 560
this.lastRaceTimesInfo = raceTimesInfos.get(selectedRaces.get(0));
562 561
}
563 562
563
+ /**
564
+ * In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, <code>null</code>
565
+ * is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change
566
+ * is signaled due to local offset / delay adjustments, no additional call to {@link #loadCompleteLeaderboard(Date)} would be required
567
+ * as <code>null</code> will be passed in any case, not being affected by local time offsets.
568
+ */
569
+ private boolean useNullAsTimePoint() {
570
+ return timer.getPlayMode() == PlayModes.Live;
571
+ }
572
+
564 573
@Override
565 574
public void timeChanged(final Date newTime, final Date oldTime) {
566 575
if (newTime != null && isMapInitialized) {
... ...
@@ -584,7 +593,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
584 593
// next, do the full thing; being the later call, if request throttling kicks in, the later call
585 594
// supersedes the earlier call which may get dropped then
586 595
GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race,
587
- newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true);
596
+ useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true);
588 597
asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY,
589 598
getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
590 599
... ...
@@ -662,7 +671,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
662 671
final GetRaceMapDataAction result;
663 672
if (!fromTimes.isEmpty()) {
664 673
result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(),
665
- race, newTime, fromTimes, toTimes, /* extrapolate */true);
674
+ race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true);
666 675
} else {
667 676
result = null;
668 677
}
... ...
@@ -787,7 +796,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
787 796
}
788 797
}
789 798
}
790
-
799
+
791 800
protected void showCourseMarksOnMap(CoursePositionsDTO courseDTO) {
792 801
if (map != null && courseDTO != null) {
793 802
Map<String, CourseMarkOverlay> toRemoveCourseMarks = new HashMap<String, CourseMarkOverlay>(courseMarkOverlays);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java
... ...
@@ -212,7 +212,10 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
212 212
result.setTailLengthInMilliseconds(tailLengthBox.getValue() == null ? -1 : tailLengthBox.getValue() * 1000l);
213 213
}
214 214
if (helpLinesSettings.isVisible(HelpLineTypes.BUOYZONE)) {
215
- result.setBuoyZoneRadiusInMeters(buoyZoneRadiusBox.getValue());
215
+ final Double value = buoyZoneRadiusBox.getValue();
216
+ if (value != null) {
217
+ result.setBuoyZoneRadiusInMeters(value);
218
+ }
216 219
}
217 220
return result;
218 221
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilterPanel.java
... ...
@@ -31,6 +31,7 @@ import com.sap.sailing.gwt.ui.client.shared.filter.FilterUIFactory;
31 31
import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI;
32 32
import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher;
33 33
import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFilterContext;
34
+import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider;
34 35
import com.sap.sailing.gwt.ui.client.shared.filter.SelectedCompetitorsFilter;
35 36
import com.sap.sailing.gwt.ui.client.shared.filter.SelectedRaceFilterContext;
36 37
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
... ...
@@ -367,6 +368,13 @@ public class CompetitorFilterPanel extends FlowPanel implements KeyUpHandler, Fi
367 368
public Button getSettingsButton() {
368 369
return settingsButton;
369 370
}
371
+
372
+ /**
373
+ * @return the {@link QuickRankProvider} if set or <code>null</code> if there is none
374
+ */
375
+ public QuickRankProvider getQuickRankProvider() {
376
+ return this.raceMap;
377
+ }
370 378
371 379
@Override
372 380
public void filterChanged(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> oldFilterSet,
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java
... ...
@@ -4,6 +4,7 @@ import java.util.ArrayList;
4 4
import java.util.Collections;
5 5
import java.util.List;
6 6
import java.util.Map;
7
+import java.util.UUID;
7 8
import java.util.logging.Logger;
8 9
9 10
import com.google.gwt.core.client.GWT;
... ...
@@ -62,32 +63,56 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint {
62 63
final boolean showRaceDetails = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_SHOW_RACE_DETAILS, false /* default*/);
63 64
final boolean embedded = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_EMBEDDED, false /* default*/);
64 65
final boolean hideToolbar = GwtHttpRequestUtils.getBooleanParameter(LeaderboardUrlSettings.PARAM_HIDE_TOOLBAR, false /* default*/);
66
+ final String eventIdAsString = GwtHttpRequestUtils.getStringParameter(LeaderboardUrlSettings.PARAM_EVENT_ID, null /* default*/);
67
+ final UUID eventId = eventIdAsString == null ? null : UUID.fromString(eventIdAsString);
65 68
66 69
leaderboardName = Window.Location.getParameter("name");
67 70
leaderboardGroupName = Window.Location.getParameter(LeaderboardUrlSettings.PARAM_LEADERBOARD_GROUP_NAME);
68 71
69 72
if (leaderboardName != null) {
70
- sailingService.checkLeaderboardName(leaderboardName,
71
- new MarkedAsyncCallback<Util.Pair<String, LeaderboardType>>(
72
- new AsyncCallback<Util.Pair<String, LeaderboardType>>() {
73
- @Override
74
- public void onSuccess(Util.Pair<String, LeaderboardType> leaderboardNameAndType) {
75
- if (leaderboardNameAndType != null
76
- && leaderboardName.equals(leaderboardNameAndType.getA())) {
77
- Window.setTitle(leaderboardName);
78
- leaderboardType = leaderboardNameAndType.getB();
79
- createUI(showRaceDetails, embedded, hideToolbar, event);
80
- } else {
81
- RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard()));
82
- }
83
- }
73
+ final Runnable checkLeaderboardNameAndCreateUI = new Runnable() {
74
+ @Override
75
+ public void run() {
76
+ sailingService.checkLeaderboardName(leaderboardName,
77
+ new MarkedAsyncCallback<Util.Pair<String, LeaderboardType>>(
78
+ new AsyncCallback<Util.Pair<String, LeaderboardType>>() {
79
+ @Override
80
+ public void onSuccess(Util.Pair<String, LeaderboardType> leaderboardNameAndType) {
81
+ if (leaderboardNameAndType != null
82
+ && leaderboardName.equals(leaderboardNameAndType.getA())) {
83
+ Window.setTitle(leaderboardName);
84
+ leaderboardType = leaderboardNameAndType.getB();
85
+ createUI(showRaceDetails, embedded, hideToolbar, event);
86
+ } else {
87
+ RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard()));
88
+ }
89
+ }
84 90
85
- @Override
86
- public void onFailure(Throwable t) {
87
- reportError("Error trying to obtain list of leaderboard names: "
88
- + t.getMessage());
89
- }
90
- }));
91
+ @Override
92
+ public void onFailure(Throwable t) {
93
+ reportError("Error trying to obtain list of leaderboard names: "
94
+ + t.getMessage());
95
+ }
96
+ }));
97
+ }
98
+ };
99
+ if (eventId == null) {
100
+ checkLeaderboardNameAndCreateUI.run(); // use null-initialized event field
101
+ } else {
102
+ sailingService.getEventById(eventId, /* withStatisticalData */false, new MarkedAsyncCallback<EventDTO>(
103
+ new AsyncCallback<EventDTO>() {
104
+ @Override
105
+ public void onFailure(Throwable caught) {
106
+ reportError("Error trying to obtain event "+eventId+": " + caught.getMessage());
107
+ }
108
+
109
+ @Override
110
+ public void onSuccess(EventDTO result) {
111
+ event = result;
112
+ checkLeaderboardNameAndCreateUI.run();
113
+ }
114
+ }));
115
+ }
91 116
} else {
92 117
RootPanel.get().add(new Label(stringMessages.noSuchLeaderboard()));
93 118
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
... ...
@@ -2150,6 +2150,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
2150 2150
CompetitorRaceRankFilter raceRankFilter = new CompetitorRaceRankFilter();
2151 2151
raceRankFilter.setLeaderboardFetcher(this);
2152 2152
raceRankFilter.setSelectedRace(preSelectedRace);
2153
+ raceRankFilter.setQuickRankProvider(this.competitorFilterPanel.getQuickRankProvider());
2153 2154
raceRankFilter.setOperator(new BinaryOperator<Integer>(BinaryOperator.Operators.LessThanEquals));
2154 2155
raceRankFilter.setValue(maxRaceRank);
2155 2156
FilterSet<CompetitorDTO, Filter<CompetitorDTO>> activeFilterSet =
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlSettings.java
... ...
@@ -6,6 +6,7 @@ import com.sap.sailing.gwt.ui.client.URLEncoder;
6 6
7 7
public class LeaderboardUrlSettings {
8 8
public static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
9
+ public static final String PARAM_EVENT_ID = "eventId";
9 10
public static final String PARAM_EMBEDDED = "embedded";
10 11
public static final String PARAM_HIDE_TOOLBAR = "hideToolbar";
11 12
public static final String PARAM_SHOW_RACE_DETAILS = "showRaceDetails";
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java
... ...
@@ -26,6 +26,8 @@ public class ScoringSchemeTypeFormatter {
26 26
return stringMessages.scoringSchemeWinnerGetsFiveIgnoringRaceCount();
27 27
case HIGH_POINT_WINNER_GETS_SIX:
28 28
return stringMessages.scoringSchemeWinnerGetsSix();
29
+ case HIGH_POINT_WINNER_GETS_SIX_IGNORING_RACE_COUNT:
30
+ return stringMessages.scoringSchemeWinnerGetsSixIgnoringRaceCount();
29 31
case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT:
30 32
return stringMessages.scoringSchemeHighPointFirstGetsTenOrEight();
31 33
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditCompetitorNameDialog.java
... ...
@@ -0,0 +1,40 @@
1
+package com.sap.sailing.gwt.ui.leaderboardedit;
2
+
3
+import com.google.gwt.user.client.ui.Grid;
4
+import com.google.gwt.user.client.ui.Label;
5
+import com.google.gwt.user.client.ui.TextBox;
6
+import com.google.gwt.user.client.ui.Widget;
7
+import com.sap.sailing.gwt.ui.client.StringMessages;
8
+import com.sap.sse.gwt.client.dialog.DataEntryDialog;
9
+
10
+public class EditCompetitorNameDialog extends DataEntryDialog<String> {
11
+ private final TextBox competitorNameBox;
12
+ private final StringMessages stringMessages;
13
+
14
+ public EditCompetitorNameDialog(StringMessages stringMessages, String competitorName,
15
+ DialogCallback<String> callback) {
16
+ super(stringMessages.competitor(), stringMessages.competitor(),
17
+ stringMessages.ok(), stringMessages.cancel(), /* validator */ null, /* animationEnabled */ true,
18
+ callback);
19
+ this.stringMessages = stringMessages;
20
+ this.competitorNameBox = createTextBox(competitorName);
21
+ }
22
+
23
+ @Override
24
+ protected String getResult() {
25
+ return this.competitorNameBox.getValue();
26
+ }
27
+
28
+ @Override
29
+ protected Widget getAdditionalWidget() {
30
+ Grid grid = new Grid(2, 2);
31
+ grid.setWidget(0, 0, new Label(stringMessages.competitor()));
32
+ grid.setWidget(0, 1, competitorNameBox);
33
+ return grid;
34
+ }
35
+
36
+ @Override
37
+ public void show() {
38
+ super.show();
39
+ }
40
+}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java
... ...
@@ -820,49 +820,49 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
820 820
return getStringMessages().suppress();
821 821
}
822 822
});
823
- result.add(new HasCell<LeaderboardRowDTO, String>() {
824
- final class OptionalBoldRenderer implements SafeHtmlRenderer<String> {
825
- private LeaderboardRowDTO currentRow;
823
+ final class OptionalBoldRenderer implements SafeHtmlRenderer<String> {
824
+ private LeaderboardRowDTO currentRow;
826 825
827
- @Override
828
- public SafeHtml render(String object) {
829
- SafeHtmlBuilder builder = new SafeHtmlBuilder();
830
- render(object, builder);
831
- return builder.toSafeHtml();
832
- }
826
+ @Override
827
+ public SafeHtml render(String object) {
828
+ SafeHtmlBuilder builder = new SafeHtmlBuilder();
829
+ render(object, builder);
830
+ return builder.toSafeHtml();
831
+ }
833 832
834
- private boolean isDisplayNameSet() {
835
- return currentRow != null && getLeaderboard().isDisplayNameSet(currentRow.competitor);
836
- }
833
+ private boolean isDisplayNameSet() {
834
+ return currentRow != null && getLeaderboard().isDisplayNameSet(currentRow.competitor);
835
+ }
837 836
838
- @Override
839
- public void render(String value, SafeHtmlBuilder builder) {
840
- if (isDisplayNameSet()) {
841
- builder.appendHtmlConstant("<b>");
842
- }
843
- builder.appendEscaped(value);
844
- if (isDisplayNameSet()) {
845
- builder.appendHtmlConstant("</b>");
846
- }
837
+ @Override
838
+ public void render(String value, SafeHtmlBuilder builder) {
839
+ if (isDisplayNameSet()) {
840
+ builder.appendHtmlConstant("<b>");
847 841
}
848
-
849
- public void setCurrentRow(LeaderboardRowDTO currentRow) {
850
- this.currentRow = currentRow;
842
+ builder.appendEscaped(value);
843
+ if (isDisplayNameSet()) {
844
+ builder.appendHtmlConstant("</b>");
851 845
}
852 846
}
853 847
854
- private final OptionalBoldRenderer renderer = new OptionalBoldRenderer();
848
+ public void setCurrentRow(LeaderboardRowDTO currentRow) {
849
+ this.currentRow = currentRow;
850
+ }
851
+ }
855 852
856
- private final EditTextCell cell = new EditTextCell(renderer) {
857
- @Override
858
- public void render(Context context, String value, SafeHtmlBuilder sb) {
859
- renderer.setCurrentRow((LeaderboardRowDTO) context.getKey());
860
- super.render(context, value, sb);
861
- }
862
- };
853
+ final OptionalBoldRenderer renderer = new OptionalBoldRenderer();
854
+
855
+ final EditTextCell cellForCompetitorName = new EditTextCell(renderer) {
856
+ @Override
857
+ public void render(Context context, String value, SafeHtmlBuilder sb) {
858
+ renderer.setCurrentRow((LeaderboardRowDTO) context.getKey());
859
+ super.render(context, value, sb);
860
+ }
861
+ };
862
+ result.add(new HasCell<LeaderboardRowDTO, String>() {
863 863
@Override
864 864
public EditTextCell getCell() {
865
- return cell;
865
+ return cellForCompetitorName;
866 866
}
867 867
868 868
@Override
... ...
@@ -875,6 +875,56 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
875 875
return getLeaderboard().getDisplayName(row.competitor);
876 876
}
877 877
});
878
+ result.add(new HasCell<LeaderboardRowDTO, String>() {
879
+ private final ButtonCell cell = new ButtonCell();
880
+ @Override
881
+ public Cell<String> getCell() {
882
+ return cell;
883
+ }
884
+
885
+ @Override
886
+ public FieldUpdater<LeaderboardRowDTO, String> getFieldUpdater() {
887
+ return new FieldUpdater<LeaderboardRowDTO, String>() {
888
+ @Override
889
+ public void update(int index, final LeaderboardRowDTO row, String valueToUpdate) {
890
+ new EditCompetitorNameDialog(getStringMessages(), row.competitor.getName(), new DialogCallback<String>() {
891
+ @Override
892
+ public void ok(final String value) {
893
+ getSailingService().updateCompetitorDisplayNameInLeaderboard(getLeaderboardName(), row.competitor.getIdAsString(),
894
+ value == null || value.length() == 0 ? null : value.trim(),
895
+ new AsyncCallback<Void>() {
896
+ @Override
897
+ public void onFailure(Throwable t) {
898
+ EditableLeaderboardPanel.this.getErrorReporter().reportError("Error trying to update display name for competitor "+
899
+ row.competitor.getName()+" in leaderboard "+getLeaderboardName()+": "+t.getMessage()+
900
+ "\nYou may have to refresh your view.");
901
+ }
902
+
903
+ @Override
904
+ public void onSuccess(Void v) {
905
+ if (getLeaderboard().competitorDisplayNames == null) {
906
+ getLeaderboard().competitorDisplayNames = new HashMap<CompetitorDTO, String>();
907
+ }
908
+ getLeaderboard().competitorDisplayNames.put(row.competitor, value == null || value.trim().length() == 0 ? null : value.trim());
909
+ cellForCompetitorName.setViewData(row, null); // ensure that getValue() is called again
910
+ EditableLeaderboardPanel.this.getData().getList().set(
911
+ EditableLeaderboardPanel.this.getData().getList().indexOf(row), row);
912
+ }
913
+ });
914
+ }
915
+ @Override
916
+ public void cancel() {
917
+ }
918
+ }).show();
919
+ }
920
+ };
921
+ }
922
+
923
+ @Override
924
+ public String getValue(LeaderboardRowDTO object) {
925
+ return getStringMessages().edit();
926
+ }
927
+ });
878 928
return result;
879 929
}
880 930
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java
... ...
@@ -1,7 +1,6 @@
1 1
package com.sap.sailing.gwt.ui.raceboard;
2 2
3 3
import java.util.ArrayList;
4
-import java.util.Collection;
5 4
import java.util.Collections;
6 5
import java.util.HashMap;
7 6
import java.util.List;
... ...
@@ -11,7 +10,6 @@ import com.google.gwt.dom.client.Document;
11 10
import com.google.gwt.dom.client.Style.Unit;
12 11
import com.google.gwt.i18n.client.DateTimeFormat;
13 12
import com.google.gwt.user.client.Command;
14
-import com.google.gwt.user.client.rpc.AsyncCallback;
15 13
import com.google.gwt.user.client.ui.Anchor;
16 14
import com.google.gwt.user.client.ui.FlowPanel;
17 15
import com.google.gwt.user.client.ui.Label;
... ...
@@ -25,7 +23,6 @@ import com.sap.sailing.domain.common.dto.FleetDTO;
25 23
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
26 24
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
27 25
import com.sap.sailing.domain.common.dto.RaceDTO;
28
-import com.sap.sailing.domain.common.media.MediaTrack;
29 26
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
30 27
import com.sap.sailing.gwt.ui.client.ErrorReporter;
31 28
import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
... ...
@@ -38,7 +35,6 @@ import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
38 35
import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
39 36
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
40 37
import com.sap.sailing.gwt.ui.client.StringMessages;
41
-import com.sap.sailing.gwt.ui.client.media.MediaComponent;
42 38
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
43 39
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
44 40
import com.sap.sailing.gwt.ui.client.shared.charts.WindChart;
... ...
@@ -215,7 +211,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
215 211
windChart.getEntryWidget().setTitle(stringMessages.windChart());
216 212
components.add(windChart);
217 213
boolean autoSelectMedia = getConfiguration().isAutoSelectMedia();
218
- MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent(selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, stringMessages, errorReporter, userAgent, user, autoSelectMedia)
214
+ MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent(selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, stringMessages, errorReporter, userAgent, user, autoSelectMedia);
219 215
leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, this.user);
220 216
componentViewers.add(leaderboardAndMapViewer);
221 217
for (ComponentViewer componentViewer : componentViewers) {
... ...
@@ -235,32 +231,6 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
235 231
}
236 232
}
237 233
238
- private MediaComponent createMediaComponent() {
239
- boolean autoSelectMedia = getConfiguration().isAutoSelectMedia();
240
- String defaultMedia = getConfiguration().getDefaultMedia();
241
- final MediaComponent mediaComponent = new MediaComponent(selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, stringMessages, errorReporter, userAgent, this.user, autoSelectMedia, defaultMedia);
242
- timer.addPlayStateListener(mediaComponent);
243
- timer.addTimeListener(mediaComponent);
244
- mediaComponent.setVisible(false);
245
- mediaService.getMediaTracksForRace(selectedRaceIdentifier, new AsyncCallback<Collection<MediaTrack>>() {
246
- @Override
247
- public void onSuccess(Collection<MediaTrack> result) {
248
- mediaComponent.getMediaLibraryCallback().onSuccess(result);
249
- if (mediaComponent.isPotentiallyPlayable(mediaComponent.getDefaultVideo())) {
250
- if (leaderboardAndMapViewer != null) {
251
- leaderboardAndMapViewer.getVideoControlButton().setVisible(true);
252
- }
253
- }
254
- }
255
-
256
- @Override
257
- public void onFailure(Throwable caught) {
258
- mediaComponent.getMediaLibraryCallback().onFailure(caught);
259
- }
260
- }); // load media tracks
261
- return mediaComponent;
262
- }
263
-
264 234
@SuppressWarnings("unused")
265 235
private <SettingsType> void addSettingsMenuItem(MenuBar settingsMenu, final Component<SettingsType> component) {
266 236
if (component.hasSettings()) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java
... ...
@@ -5,24 +5,17 @@ import java.util.List;
5 5
6 6
import com.google.gwt.dom.client.Document;
7 7
import com.google.gwt.dom.client.Style.Unit;
8
-import com.google.gwt.event.dom.client.ClickEvent;
9
-import com.google.gwt.event.dom.client.ClickHandler;
10
-import com.google.gwt.event.logical.shared.CloseEvent;
11
-import com.google.gwt.event.logical.shared.CloseHandler;
12 8
import com.google.gwt.safehtml.shared.SafeHtml;
13
-import com.google.gwt.user.client.Window;
14 9
import com.google.gwt.user.client.ui.AbsolutePanel;
15 10
import com.google.gwt.user.client.ui.Button;
16 11
import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
17 12
import com.google.gwt.user.client.ui.LayoutPanel;
18 13
import com.google.gwt.user.client.ui.Panel;
19
-import com.google.gwt.user.client.ui.PopupPanel;
20 14
import com.google.gwt.user.client.ui.RequiresResize;
21 15
import com.google.gwt.user.client.ui.ScrollPanel;
22 16
import com.google.gwt.user.client.ui.Widget;
23 17
import com.google.gwt.user.client.ui.WidgetCollection;
24 18
import com.sap.sailing.gwt.ui.client.StringMessages;
25
-import com.sap.sailing.gwt.ui.client.media.MediaComponent;
26 19
import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
27 20
import com.sap.sailing.gwt.ui.client.shared.components.Component;
28 21
import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer;
... ...
@@ -30,7 +23,6 @@ import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog;
30 23
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
31 24
import com.sap.sailing.gwt.ui.shared.UserDTO;
32 25
import com.sap.sse.common.Util.Pair;
33
-import com.sap.sse.gwt.client.dialog.WindowBox;
34 26
35 27
/**
36 28
* Component Viewer that uses a {@link TouchSplitLayoutPanel}
... ...
@@ -63,8 +55,10 @@ public class SideBySideComponentViewer implements ComponentViewer {
63 55
private final Component<?> rightComponent;
64 56
private final List<Component<?>> components;
65 57
private final ScrollPanel leftScrollPanel;
66
- private final Button videoControlButton;
67 58
private final StringMessages stringMessages;
59
+ private final MediaPlayerManagerComponent mediaPlayerManagerComponent;
60
+ private final Button videoToggleButton;
61
+ private final Button mediaManagementButton;
68 62
69 63
private LayoutPanel mainPanel;
70 64
... ...
@@ -76,8 +70,10 @@ public class SideBySideComponentViewer implements ComponentViewer {
76 70
this.stringMessages = stringMessages;
77 71
this.leftComponent = leftComponentP;
78 72
this.rightComponent = rightComponentP;
73
+ this.mediaPlayerManagerComponent = mediaPlayerManagerComponent;
79 74
this.components = components;
80
- this.videoControlButton = createVideoControlButton(mediaPlayerManagerComponent);
75
+ this.videoToggleButton = createVideoToggleButton(mediaPlayerManagerComponent);
76
+ this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent);
81 77
this.leftScrollPanel = new ScrollPanel();
82 78
this.leftScrollPanel.add(leftComponentP.getEntryWidget());
83 79
this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle());
... ...
@@ -102,9 +98,9 @@ public class SideBySideComponentViewer implements ComponentViewer {
102 98
103 99
// add additional toggle buttons panel that currently only contains the video button
104 100
List<Pair<Button, Component<?>>> additionalVerticalButtons = new ArrayList<Pair<Button,Component<?>>>();
105
- additionalVerticalButtons.add(new Pair<Button, Component<?>>(videoControlButton, mediaComponent));
101
+ additionalVerticalButtons.add(new Pair<Button, Component<?>>(videoToggleButton, this.mediaPlayerManagerComponent));
106 102
if (user != null) {
107
- additionalVerticalButtons.add(new Pair<Button, Component<?>>(mediaComponent.getMediaSelectionButton(), mediaComponent));
103
+ additionalVerticalButtons.add(new Pair<Button, Component<?>>(mediaManagementButton, this.mediaPlayerManagerComponent));
108 104
}
109 105
110 106
// ensure that toggle buttons are positioned right
... ...
@@ -112,10 +108,10 @@ public class SideBySideComponentViewer implements ComponentViewer {
112 108
}
113 109
114 110
/**
115
- * Create the video control button that shows or hides the video popup
111
+ * Create the video toggle button that shows or hides the video popup
116 112
*/
117
- private Button createVideoControlButton(final MediaPlayerManagerComponent mediaPlayerManagerComponent) {
118
- final Button videoControlButton = new Button(new SafeHtml() {
113
+ private Button createVideoToggleButton(final MediaPlayerManagerComponent mediaPlayerManagerComponent) {
114
+ final Button videoToggleButton = new Button(new SafeHtml() {
119 115
private static final long serialVersionUID = 8679639887708833213L;
120 116
@Override
121 117
public String asString() {
... ...
@@ -126,46 +122,23 @@ public class SideBySideComponentViewer implements ComponentViewer {
126 122
}
127 123
}
128 124
});
129
- videoControlButton.setTitle(stringMessages.showVideoPopup());
130
- Button closeButton = new Button();
131
- closeButton.setStyleName("VideoPopup-Close-Button");
132
- final WindowBox dialog = new WindowBox(stringMessages.videoComponentShortName(), stringMessages.videoComponentShortName(), mediaComponent.getEntryWidget(), null);
133
- dialog.addCloseHandler(new CloseHandler<PopupPanel>() {
134
- @Override
135
- public void onClose(CloseEvent<PopupPanel> event) {
136
- mediaPlayerManagerComponent.setVisible(false);
137
- if (Document.get().getClientWidth() > 1024) {
138
- videoControlButton.setText(stringMessages.showVideoPopup());
139
- }
140
- }
141
- });
142
- videoControlButton.addClickHandler(new ClickHandler() {
143
- @Override
144
- public void onClick(ClickEvent event) {
145
- if (!dialog.isShowing()) {
146
- if (mediaComponent.isPotentiallyPlayable(mediaComponent.getDefaultVideo())) {
147
- mediaComponent.setVisible(true);
148
- dialog.setPopupPosition(47, Document.get().getClientHeight()-355);
149
- dialog.show();
150
- if (Document.get().getClientWidth() > 1024) {
151
- videoControlButton.setText(stringMessages.hideVideoPopup());
152
- }
153
- } else {
154
- Window.alert("This race has no default video associated.");
155
- }
156
- } else {
157
- mediaComponent.setVisible(false);
158
- dialog.hide();
159
- if (Document.get().getClientWidth() > 1024) {
160
- videoControlButton.setText(stringMessages.showVideoPopup());
161
- }
162
- }
163
- }
164
- });
125
+ videoToggleButton.setTitle(stringMessages.showVideoPopup());
126
+ // hide button initially as we defer showing the button to the asynchroneous
127
+ // task that gets launched by the media service to get video tracks
128
+ videoToggleButton.setVisible(false);
129
+ return videoToggleButton;
130
+ }
131
+
132
+ /**
133
+ * Create the video control button that shows or hides the video popup
134
+ */
135
+ private Button createMediaManagementButton(final MediaPlayerManagerComponent mediaPlayerManagerComponent) {
136
+ final Button mediaManagementButton = new Button(stringMessages.mediaPanel());
137
+ mediaManagementButton.setTitle(stringMessages.showVideoPopup());
165 138
// hide button initially as we defer showing the button to the asynchroneous
166 139
// task that gets launched by the media service to get video tracks
167
- videoControlButton.setVisible(false);
168
- return videoControlButton;
140
+ mediaManagementButton.setVisible(false);
141
+ return mediaManagementButton;
169 142
}
170 143
171 144
private void initializeComponents() {
... ...
@@ -181,7 +154,7 @@ public class SideBySideComponentViewer implements ComponentViewer {
181 154
}
182 155
183 156
public Button getVideoControlButton() {
184
- return videoControlButton;
157
+ return videoToggleButton;
185 158
}
186 159
187 160
/**
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java
... ...
@@ -5,6 +5,12 @@ import org.osgi.framework.BundleContext;
5 5
6 6
public class Activator implements BundleActivator {
7 7
private static BundleContext context;
8
+ private SailingServiceImpl sailingServiceToStopWhenStopping;
9
+ private static Activator INSTANCE;
10
+
11
+ public Activator() {
12
+ INSTANCE = this;
13
+ }
8 14
9 15
@Override
10 16
public void start(BundleContext context) throws Exception {
... ...
@@ -13,10 +19,24 @@ public class Activator implements BundleActivator {
13 19
14 20
@Override
15 21
public void stop(BundleContext context) throws Exception {
22
+ if (sailingServiceToStopWhenStopping != null) {
23
+ sailingServiceToStopWhenStopping.stop();
24
+ }
25
+ }
26
+
27
+ public static Activator getInstance() {
28
+ if (INSTANCE == null) {
29
+ INSTANCE = new Activator();
30
+ }
31
+ return INSTANCE;
16 32
}
17 33
18 34
public static BundleContext getDefault() {
19 35
return context;
20 36
}
21 37
38
+ public void setSailingService(SailingServiceImpl sailingServiceImpl) {
39
+ sailingServiceToStopWhenStopping = sailingServiceImpl;
40
+ }
41
+
22 42
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java
... ...
@@ -0,0 +1,224 @@
1
+package com.sap.sailing.gwt.ui.server;
2
+
3
+import java.lang.ref.Reference;
4
+import java.lang.ref.ReferenceQueue;
5
+import java.lang.ref.WeakReference;
6
+import java.util.HashMap;
7
+import java.util.List;
8
+import java.util.Map;
9
+import java.util.logging.Level;
10
+import java.util.logging.Logger;
11
+
12
+import com.sap.sailing.domain.base.Competitor;
13
+import com.sap.sailing.domain.base.Mark;
14
+import com.sap.sailing.domain.base.Waypoint;
15
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
16
+import com.sap.sailing.domain.common.TimePoint;
17
+import com.sap.sailing.domain.common.WindSource;
18
+import com.sap.sailing.domain.tracking.GPSFix;
19
+import com.sap.sailing.domain.tracking.GPSFixMoving;
20
+import com.sap.sailing.domain.tracking.MarkPassing;
21
+import com.sap.sailing.domain.tracking.RaceChangeListener;
22
+import com.sap.sailing.domain.tracking.TrackedRace;
23
+import com.sap.sailing.domain.tracking.TrackedRaceStatus;
24
+import com.sap.sailing.domain.tracking.Wind;
25
+import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
26
+import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
27
+import com.sap.sailing.server.masterdata.DummyTrackedRace;
28
+import com.sap.sailing.util.SmartFutureCache;
29
+import com.sap.sailing.util.SmartFutureCache.AbstractCacheUpdater;
30
+import com.sap.sailing.util.SmartFutureCache.UpdateInterval;
31
+
32
+/**
33
+ * Calculating the quick ranks for many clients for a live race is expensive and therefore benefits from consolidation
34
+ * in a single cache. This cache needs to listen for changes in the races for which it manages those {@link QuickRankDTO} objects
35
+ * and trigger a re-calculation. It uses a {@link SmartFutureCache} to store and update the cache entries. The keys of the
36
+ * {@link SmartFutureCache} are {@link RegattaAndRaceIdentifier}s. In order to properly evict cache entries when the race
37
+ * is no longer reachable, each {@link TrackedRace} is referenced by a {@link WeakReference} which has a queue associated.
38
+ * The cache runs a thread that fetches collected references from the queue and evicts the cache entries for the respective
39
+ * race identifiers.
40
+ *
41
+ * @author Axel Uhl (D043530)
42
+ *
43
+ */
44
+public class QuickRanksLiveCache extends AbstractRaceChangeListener {
45
+ private static final Logger logger = Logger.getLogger(QuickRanksLiveCache.class.getName());
46
+ /**
47
+ * For each weak reference to a tracked race, remembers the race's {@link RegattaAndRaceIdentifier} which is then the key
48
+ * into the {@link SmartFutureCache} from which entries that are no longer referenced shall be removed.
49
+ */
50
+ private final Map<WeakReference<? extends TrackedRace>, RegattaAndRaceIdentifier> fromRefToRaceIdentifier;
51
+
52
+ private final ReferenceQueue<? extends TrackedRace> referencesToGarbageCollectedRaces;
53
+
54
+ /**
55
+ * To reliably stop the thread we need a specific reference getting enqueued that we can recognize. Therefore,
56
+ * we create a dummy tracked race here and release the reference to it as soon as the {@link #stop} method is called.
57
+ * When this reference is later enqueued, the thread will terminate.
58
+ */
59
+ private TrackedRace dummyTrackedRace = new DummyTrackedRace("Dummy for QuickRanksLiveCache stopping", /* raceId */
60
+ "Dummy for QuickRanksLiveCache stopping");
61
+
62
+ private final WeakReference<? extends TrackedRace> stopRef = new WeakReference<TrackedRace>(dummyTrackedRace);
63
+
64
+ private final SmartFutureCache<RegattaAndRaceIdentifier, List<QuickRankDTO>, CalculateOrPurge> cache;
65
+
66
+ private final SailingServiceImpl service;
67
+
68
+ private static class CalculateOrPurge implements UpdateInterval<CalculateOrPurge> {
69
+ private static final CalculateOrPurge CALCULATE = new CalculateOrPurge();
70
+ private static final CalculateOrPurge PURGE = new CalculateOrPurge();
71
+
72
+ @Override
73
+ public CalculateOrPurge join(CalculateOrPurge otherUpdateInterval) {
74
+ final CalculateOrPurge result;
75
+ if (this == PURGE || otherUpdateInterval == PURGE) {
76
+ result = PURGE;
77
+ } else {
78
+ result = CALCULATE;
79
+ }
80
+ return result;
81
+ }
82
+ }
83
+
84
+ public QuickRanksLiveCache(final SailingServiceImpl service) {
85
+ this.service = service;
86
+ cache = new SmartFutureCache<RegattaAndRaceIdentifier, List<QuickRankDTO>, CalculateOrPurge>(
87
+ new AbstractCacheUpdater<RegattaAndRaceIdentifier, List<QuickRankDTO>, CalculateOrPurge>() {
88
+ @Override
89
+ public List<QuickRankDTO> computeCacheUpdate(RegattaAndRaceIdentifier key,
90
+ CalculateOrPurge updateInterval) throws Exception {
91
+ logger.fine("Computing cache update for live QuickRanks of race "+key);
92
+ final List<QuickRankDTO> result;
93
+ if (updateInterval == CalculateOrPurge.PURGE) {
94
+ result = null;
95
+ } else {
96
+ result = service.computeQuickRanks(key, /* time point; null means live */ null);
97
+ }
98
+ return result;
99
+ }
100
+ }, getClass().getName());
101
+ fromRefToRaceIdentifier = new HashMap<>();
102
+ new Thread("QuickRanksLiveCache garbage collector") {
103
+ @Override
104
+ public void run() {
105
+ Reference<?> ref;
106
+ do {
107
+ try {
108
+ ref = referencesToGarbageCollectedRaces.remove();
109
+ if (ref != stopRef) {
110
+ RegattaAndRaceIdentifier raceIdentifier = fromRefToRaceIdentifier.get(ref);
111
+ remove(raceIdentifier);
112
+ }
113
+ } catch (InterruptedException e) {
114
+ logger.log(Level.INFO, "Interrupted while waiting for reference in reference queue; quitting", e);
115
+ break;
116
+ }
117
+ } while (ref != stopRef);
118
+ logger.info("Received stop in QuickRanksLiveCache garbage collector; terminating");
119
+ }
120
+ }.start();
121
+ referencesToGarbageCollectedRaces = new ReferenceQueue<>();
122
+ }
123
+
124
+ private void remove(RegattaAndRaceIdentifier raceIdentifier) {
125
+ cache.remove(raceIdentifier);
126
+ }
127
+
128
+ public void stop() {
129
+ dummyTrackedRace = null; // release the dummy tracked race, causing the stopRef to be enqueued
130
+ }
131
+
132
+ public List<QuickRankDTO> get(RegattaAndRaceIdentifier raceIdentifier) {
133
+ List<QuickRankDTO> result = cache.get(raceIdentifier, false);
134
+ if (result == null) {
135
+ TrackedRace trackedRace = service.getExistingTrackedRace(raceIdentifier);
136
+ if (trackedRace != null) {
137
+ trackedRace.addListener(new Listener(raceIdentifier)); // register for all changes that may affect the quick ranks
138
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
139
+ }
140
+ }
141
+ result = cache.get(raceIdentifier, /* wait for latest result */ true);
142
+ return result;
143
+ }
144
+
145
+ private class Listener implements RaceChangeListener {
146
+ private final RegattaAndRaceIdentifier raceIdentifier;
147
+
148
+ public Listener(RegattaAndRaceIdentifier raceIdentifier) {
149
+ this.raceIdentifier = raceIdentifier;
150
+ }
151
+
152
+ @Override
153
+ public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
154
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
155
+ }
156
+
157
+ @Override
158
+ public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
159
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
160
+ }
161
+
162
+ @Override
163
+ public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
164
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
165
+ }
166
+
167
+ @Override
168
+ public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
169
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
170
+ }
171
+
172
+ @Override
173
+ public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
174
+ Iterable<MarkPassing> markPassings) {
175
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
176
+ }
177
+
178
+ @Override
179
+ public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
180
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
181
+ }
182
+
183
+ @Override
184
+ public void windDataReceived(Wind wind, WindSource windSource) {
185
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
186
+ }
187
+
188
+ @Override
189
+ public void windDataRemoved(Wind wind, WindSource windSource) {
190
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
191
+ }
192
+
193
+ @Override
194
+ public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
195
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
196
+ }
197
+
198
+ @Override
199
+ public void raceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, TimePoint startTimeReceived) {
200
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
201
+ }
202
+
203
+ @Override
204
+ public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
205
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
206
+ }
207
+
208
+ @Override
209
+ public void delayToLiveChanged(long delayToLiveInMillis) {
210
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
211
+ }
212
+
213
+ @Override
214
+ public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
215
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
216
+ }
217
+
218
+ @Override
219
+ public void statusChanged(TrackedRaceStatus newStatus) {
220
+ cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
221
+ }
222
+ }
223
+
224
+}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
... ...
@@ -453,10 +453,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
453 453
454 454
private final SwissTimingReplayService swissTimingReplayService;
455 455
456
- private final BundleContext context;
456
+ private final QuickRanksLiveCache quickRanksLiveCache;
457 457
458 458
public SailingServiceImpl() {
459
- context = Activator.getDefault();
459
+ BundleContext context = Activator.getDefault();
460
+ Activator activator = Activator.getInstance();
461
+ if (context != null) {
462
+ activator.setSailingService(this); // register so this service is informed when the bundle shuts down
463
+ }
464
+ quickRanksLiveCache = new QuickRanksLiveCache(this);
460 465
racingEventServiceTracker = createAndOpenRacingEventServiceTracker(context);
461 466
replicationServiceTracker = createAndOpenReplicationServiceTracker(context);
462 467
resultUrlRegistryServiceTracker = createAndOpenResultUrlRegistryServiceTracker(context);
... ...
@@ -501,6 +506,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
501 506
/* keepAliveTime */ 60, TimeUnit.SECONDS,
502 507
/* workQueue */ new LinkedBlockingQueue<Runnable>());
503 508
}
509
+
510
+ /**
511
+ * Stops this service and frees its resources. In particular, caching services and threads owned by this service will be
512
+ * notified to stop their jobs.
513
+ */
514
+ public void stop() {
515
+ quickRanksLiveCache.stop();
516
+ }
504 517
505 518
protected SwissTimingReplayService getSwissTimingReplayService(BundleContext context) {
506 519
return createAndOpenSwissTimingReplayServiceTracker(context).getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory());
... ...
@@ -1719,21 +1732,24 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
1719 1732
1720 1733
private List<SidelineDTO> getCourseSidelines(RegattaAndRaceIdentifier raceIdentifier, Date date) {
1721 1734
List<SidelineDTO> result = new ArrayList<SidelineDTO>();
1722
- if (date != null) {
1723
- TimePoint dateAsTimePoint = new MillisecondsTimePoint(date);
1724
- TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
1725
- if (trackedRace != null) {
1726
- for (Sideline sideline : trackedRace.getCourseSidelines()) {
1727
- List<MarkDTO> markDTOs = new ArrayList<MarkDTO>();
1728
- for (Mark mark : sideline.getMarks()) {
1729
- GPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(mark);
1730
- Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false);
1731
- if (positionAtDate != null) {
1732
- markDTOs.add(convertToMarkDTO(mark, positionAtDate));
1733
- }
1735
+ final TimePoint dateAsTimePoint;
1736
+ TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
1737
+ if (trackedRace != null) {
1738
+ if (date == null) {
1739
+ dateAsTimePoint = MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis());
1740
+ } else {
1741
+ dateAsTimePoint = new MillisecondsTimePoint(date);
1742
+ }
1743
+ for (Sideline sideline : trackedRace.getCourseSidelines()) {
1744
+ List<MarkDTO> markDTOs = new ArrayList<MarkDTO>();
1745
+ for (Mark mark : sideline.getMarks()) {
1746
+ GPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(mark);
1747
+ Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false);
1748
+ if (positionAtDate != null) {
1749
+ markDTOs.add(convertToMarkDTO(mark, positionAtDate));
1734 1750
}
1735
- result.add(new SidelineDTO(sideline.getName(), markDTOs));
1736 1751
}
1752
+ result.add(new SidelineDTO(sideline.getName(), markDTOs));
1737 1753
}
1738 1754
}
1739 1755
return result;
... ...
@@ -1742,59 +1758,71 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
1742 1758
@Override
1743 1759
public CoursePositionsDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date) {
1744 1760
CoursePositionsDTO result = new CoursePositionsDTO();
1745
- if (date != null) {
1746
- TimePoint dateAsTimePoint = new MillisecondsTimePoint(date);
1747
- TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
1748
- if (trackedRace != null) {
1749
- result.marks = new HashSet<MarkDTO>();
1750
- result.waypointPositions = new ArrayList<PositionDTO>();
1751
- Set<Mark> marks = new HashSet<Mark>();
1752
- Course course = trackedRace.getRace().getCourse();
1753
- for (Waypoint waypoint : course.getWaypoints()) {
1754
- Position waypointPosition = trackedRace.getApproximatePosition(waypoint, dateAsTimePoint);
1755
- if (waypointPosition != null) {
1756
- result.waypointPositions.add(new PositionDTO(waypointPosition.getLatDeg(), waypointPosition.getLngDeg()));
1757
- }
1758
- for (Mark b : waypoint.getMarks()) {
1759
- marks.add(b);
1760
- }
1761
+ TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
1762
+ if (trackedRace != null) {
1763
+ final TimePoint dateAsTimePoint;
1764
+ if (date == null) {
1765
+ dateAsTimePoint = MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis());
1766
+ } else {
1767
+ dateAsTimePoint = new MillisecondsTimePoint(date);
1768
+ }
1769
+ result.marks = new HashSet<MarkDTO>();
1770
+ result.waypointPositions = new ArrayList<PositionDTO>();
1771
+ Set<Mark> marks = new HashSet<Mark>();
1772
+ Course course = trackedRace.getRace().getCourse();
1773
+ for (Waypoint waypoint : course.getWaypoints()) {
1774
+ Position waypointPosition = trackedRace.getApproximatePosition(waypoint, dateAsTimePoint);
1775
+ if (waypointPosition != null) {
1776
+ result.waypointPositions.add(new PositionDTO(waypointPosition.getLatDeg(), waypointPosition
1777
+ .getLngDeg()));
1761 1778
}
1762
- for (Mark mark : marks) {
1763
- GPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(mark);
1764
- Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false);
1765
- if (positionAtDate != null) {
1766
- result.marks.add(convertToMarkDTO(mark, positionAtDate));
1767
- }
1779
+ for (Mark b : waypoint.getMarks()) {
1780
+ marks.add(b);
1768 1781
}
1782
+ }
1783
+ for (Mark mark : marks) {
1784
+ GPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(mark);
1785
+ Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false);
1786
+ if (positionAtDate != null) {
1787
+ result.marks.add(convertToMarkDTO(mark, positionAtDate));
1788
+ }
1789
+ }
1769 1790
1770
- // set the positions of start and finish
1771
- Waypoint firstWaypoint = course.getFirstWaypoint();
1772
- if (firstWaypoint != null && Util.size(firstWaypoint.getMarks())==2) {
1773
- final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getStartLine(dateAsTimePoint);
1774
- if (markPositionDTOsAndLineAdvantage != null) {
1775
- final List<PositionDTO> startMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace, firstWaypoint);
1776
- result.startMarkPositions = startMarkPositionDTOs;
1777
- result.startLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters();
1778
- Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage.getAbsoluteAngleDifferenceToTrueWind();
1779
- result.startLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind==null?null:absoluteAngleDifferenceToTrueWind.getDegrees();
1780
- result.startLineAdvantageousSide = markPositionDTOsAndLineAdvantage.getAdvantageousSideWhileApproachingLine();
1781
- Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage();
1782
- result.startLineAdvantageInMeters = advantage==null?null:advantage.getMeters();
1783
- }
1784
- }
1785
- Waypoint lastWaypoint = course.getLastWaypoint();
1786
- if (lastWaypoint != null && Util.size(lastWaypoint.getMarks())==2) {
1787
- final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getFinishLine(dateAsTimePoint);
1788
- if (markPositionDTOsAndLineAdvantage != null) {
1789
- final List<PositionDTO> finishMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace, lastWaypoint);
1790
- result.finishMarkPositions = finishMarkPositionDTOs;
1791
- result.finishLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters();
1792
- Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage.getAbsoluteAngleDifferenceToTrueWind();
1793
- result.finishLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind==null?null:absoluteAngleDifferenceToTrueWind.getDegrees();
1794
- result.finishLineAdvantageousSide = markPositionDTOsAndLineAdvantage.getAdvantageousSideWhileApproachingLine();
1795
- Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage();
1796
- result.finishLineAdvantageInMeters = advantage==null?null:advantage.getMeters();
1797
- }
1791
+ // set the positions of start and finish
1792
+ Waypoint firstWaypoint = course.getFirstWaypoint();
1793
+ if (firstWaypoint != null && Util.size(firstWaypoint.getMarks()) == 2) {
1794
+ final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getStartLine(dateAsTimePoint);
1795
+ if (markPositionDTOsAndLineAdvantage != null) {
1796
+ final List<PositionDTO> startMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace,
1797
+ firstWaypoint);
1798
+ result.startMarkPositions = startMarkPositionDTOs;
1799
+ result.startLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters();
1800
+ Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage
1801
+ .getAbsoluteAngleDifferenceToTrueWind();
1802
+ result.startLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind == null ? null
1803
+ : absoluteAngleDifferenceToTrueWind.getDegrees();
1804
+ result.startLineAdvantageousSide = markPositionDTOsAndLineAdvantage
1805
+ .getAdvantageousSideWhileApproachingLine();
1806
+ Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage();
1807
+ result.startLineAdvantageInMeters = advantage == null ? null : advantage.getMeters();
1808
+ }
1809
+ }
1810
+ Waypoint lastWaypoint = course.getLastWaypoint();
1811
+ if (lastWaypoint != null && Util.size(lastWaypoint.getMarks()) == 2) {
1812
+ final LineDetails markPositionDTOsAndLineAdvantage = trackedRace.getFinishLine(dateAsTimePoint);
1813
+ if (markPositionDTOsAndLineAdvantage != null) {
1814
+ final List<PositionDTO> finishMarkPositionDTOs = getMarkPositionDTOs(dateAsTimePoint, trackedRace,
1815
+ lastWaypoint);
1816
+ result.finishMarkPositions = finishMarkPositionDTOs;
1817
+ result.finishLineLengthInMeters = markPositionDTOsAndLineAdvantage.getLength().getMeters();
1818
+ Bearing absoluteAngleDifferenceToTrueWind = markPositionDTOsAndLineAdvantage
1819
+ .getAbsoluteAngleDifferenceToTrueWind();
1820
+ result.finishLineAngleToCombinedWind = absoluteAngleDifferenceToTrueWind == null ? null
1821
+ : absoluteAngleDifferenceToTrueWind.getDegrees();
1822
+ result.finishLineAdvantageousSide = markPositionDTOsAndLineAdvantage
1823
+ .getAdvantageousSideWhileApproachingLine();
1824
+ Distance advantage = markPositionDTOsAndLineAdvantage.getAdvantage();
1825
+ result.finishLineAdvantageInMeters = advantage == null ? null : advantage.getMeters();
1798 1826
}
1799 1827
}
1800 1828
}
... ...
@@ -1908,28 +1936,48 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
1908 1936
return markPositionDTOs;
1909 1937
}
1910 1938
1911
- private List<QuickRankDTO> getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date) throws NoWindException {
1939
+ /**
1940
+ * @param timePoint
1941
+ * <code>null</code> means "live" and is then replaced by "now" minus the tracked race's
1942
+ * {@link TrackedRace#getDelayToLiveInMillis() delay}.
1943
+ */
1944
+ public List<QuickRankDTO> computeQuickRanks(RegattaAndRaceIdentifier raceIdentifier, TimePoint timePoint)
1945
+ throws NoWindException {
1912 1946
List<QuickRankDTO> result = new ArrayList<QuickRankDTO>();
1913
- if (date != null) {
1914
- TimePoint dateAsTimePoint = new MillisecondsTimePoint(date);
1915
- TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
1916
- if (trackedRace != null) {
1917
- RaceDefinition race = trackedRace.getRace();
1918
- int rank = 1;
1919
- for (Competitor competitor : trackedRace.getCompetitorsFromBestToWorst(dateAsTimePoint)) {
1920
- TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, dateAsTimePoint);
1921
- if (trackedLeg != null) {
1922
- int legNumberOneBased = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1;
1923
- QuickRankDTO quickRankDTO = new QuickRankDTO(baseDomainFactory.convertToCompetitorDTO(competitor), rank, legNumberOneBased);
1924
- result.add(quickRankDTO);
1925
- }
1926
- rank++;
1947
+ TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
1948
+ if (trackedRace != null) {
1949
+ final TimePoint actualTimePoint;
1950
+ if (timePoint == null) {
1951
+ actualTimePoint = MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis());
1952
+ } else {
1953
+ actualTimePoint = timePoint;
1954
+ }
1955
+ RaceDefinition race = trackedRace.getRace();
1956
+ int rank = 1;
1957
+ for (Competitor competitor : trackedRace.getCompetitorsFromBestToWorst(actualTimePoint)) {
1958
+ TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, actualTimePoint);
1959
+ if (trackedLeg != null) {
1960
+ int legNumberOneBased = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1;
1961
+ QuickRankDTO quickRankDTO = new QuickRankDTO(baseDomainFactory.convertToCompetitorDTO(competitor),
1962
+ rank, legNumberOneBased);
1963
+ result.add(quickRankDTO);
1927 1964
}
1965
+ rank++;
1928 1966
}
1929 1967
}
1930 1968
return result;
1931 1969
}
1932 1970
1971
+ private List<QuickRankDTO> getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date) throws NoWindException {
1972
+ final List<QuickRankDTO> result;
1973
+ if (date == null) {
1974
+ result = quickRanksLiveCache.get(raceIdentifier);
1975
+ } else {
1976
+ result = computeQuickRanks(raceIdentifier, date == null ? null : new MillisecondsTimePoint(date));
1977
+ }
1978
+ return result;
1979
+ }
1980
+
1933 1981
@Override
1934 1982
public void setRaceIsKnownToStartUpwind(RegattaAndRaceIdentifier raceIdentifier, boolean raceIsKnownToStartUpwind) {
1935 1983
getService().apply(new SetRaceIsKnownToStartUpwind(raceIdentifier, raceIsKnownToStartUpwind));
... ...
@@ -3041,7 +3089,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
3041 3089
public List<EventBaseDTO> getPublicEventsOfAllSailingServers() throws MalformedURLException {
3042 3090
List<EventBaseDTO> result = new ArrayList<>();
3043 3091
for (EventDTO localEvent : getEvents()) {
3044
- if(localEvent.isPublic) {
3092
+ if (localEvent.isPublic) {
3045 3093
result.add(localEvent);
3046 3094
}
3047 3095
}
... ...
@@ -3140,14 +3188,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
3140 3188
}
3141 3189
3142 3190
@Override
3143
- public EventDTO createEvent(String eventName, Date startDate, Date endDate, String venue, boolean isPublic, List<String> courseAreaNames,
3144
- Iterable<String> imageURLs, Iterable<String> videoURLs) throws MalformedURLException {
3191
+ public EventDTO createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue,
3192
+ boolean isPublic, List<String> courseAreaNames, Iterable<String> imageURLs,
3193
+ Iterable<String> videoURLs, Iterable<String> sponsorImageURLs, String logoImageURLAsString, String officialWebsiteURLAsString)
3194
+ throws MalformedURLException {
3145 3195
UUID eventUuid = UUID.randomUUID();
3146 3196
TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null;
3147 3197
TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null;
3148 3198
getService().apply(
3149
- new CreateEvent(eventName, startTimePoint, endTimePoint, venue, isPublic, eventUuid, createURLsFromStrings(imageURLs),
3150
- createURLsFromStrings(videoURLs)));
3199
+ new CreateEvent(eventName, eventDescription, startTimePoint, endTimePoint, venue, isPublic, eventUuid,
3200
+ createURLsFromStrings(imageURLs), createURLsFromStrings(videoURLs),
3201
+ createURLsFromStrings(sponsorImageURLs),
3202
+ logoImageURLAsString == null ? null : new URL(logoImageURLAsString), officialWebsiteURLAsString == null ? null : new URL(officialWebsiteURLAsString)));
3151 3203
for (String courseAreaName : courseAreaNames) {
3152 3204
createCourseArea(eventUuid, courseAreaName);
3153 3205
}
... ...
@@ -3219,17 +3271,32 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
3219 3271
eventDTO.id = (UUID) event.getId();
3220 3272
eventDTO.setDescription(event.getDescription());
3221 3273
eventDTO.setOfficialWebsiteURL(event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null);
3222
- eventDTO.setLogoImageURL(event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null);
3274
+ if (event.getLogoImageURL() == null) {
3275
+ eventDTO.setLogoImageURL(null);
3276
+ } else {
3277
+ eventDTO.setLogoImageURL(event.getLogoImageURL().toString());
3278
+ setImageSize(event, eventDTO, event.getLogoImageURL());
3279
+ }
3223 3280
for(URL url: event.getSponsorImageURLs()) {
3224 3281
eventDTO.addSponsorImageURL(url.toString());
3282
+ setImageSize(event, eventDTO, url);
3225 3283
}
3226 3284
for(URL url: event.getImageURLs()) {
3227 3285
eventDTO.addImageURL(url.toString());
3286
+ setImageSize(event, eventDTO, url);
3228 3287
}
3229 3288
for(URL url: event.getVideoURLs()) {
3230 3289
eventDTO.addVideoURL(url.toString());
3231 3290
}
3232 3291
}
3292
+
3293
+ private void setImageSize(EventBase event, EventBaseDTO eventDTO, URL imageURL) {
3294
+ try {
3295
+ eventDTO.setImageSize(imageURL.toString(), event.getImageSize(imageURL));
3296
+ } catch (InterruptedException | ExecutionException e) {
3297
+ logger.log(Level.FINE, "Was unable to obtain image size for "+imageURL+" earlier.", e);
3298
+ }
3299
+ }
3233 3300
3234 3301
private EventDTO convertToEventDTO(Event event, boolean withStatisticalData) {
3235 3302
EventDTO eventDTO = new EventDTO(event.getName());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java
... ...
@@ -34,9 +34,12 @@ public class CompactRaceMapDataDTO implements IsSerializable {
34 34
for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : boatPositions.entrySet()) {
35 35
this.boatPositionsByCompetitorIdAsString.put(e.getKey().getIdAsString(), e.getValue());
36 36
}
37
- this.quickRanks = new ArrayList<CompactQuickRankDTO>(quickRanks.size());
38
- for (QuickRankDTO quickRank : quickRanks) {
39
- this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.rank, quickRank.legNumberOneBased));
37
+ this.quickRanks = new ArrayList<CompactQuickRankDTO>(quickRanks == null ? 0 : quickRanks.size());
38
+ if (quickRanks != null) {
39
+ for (QuickRankDTO quickRank : quickRanks) {
40
+ this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.rank,
41
+ quickRank.legNumberOneBased));
42
+ }
40 43
}
41 44
this.courseSidelines = courseSidelines;
42 45
this.coursePositions = coursePositions;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java
... ...
@@ -1,15 +1,22 @@
1 1
package com.sap.sailing.gwt.ui.shared;
2 2
3 3
import java.util.ArrayList;
4
+import java.util.Collections;
5
+import java.util.Comparator;
4 6
import java.util.Date;
7
+import java.util.HashMap;
5 8
import java.util.List;
9
+import java.util.Map;
6 10
import java.util.UUID;
7 11
8 12
import com.google.gwt.user.client.rpc.IsSerializable;
13
+import com.sap.sailing.domain.common.ImageSize;
9 14
import com.sap.sailing.domain.common.dto.NamedDTO;
15
+import com.sap.sse.common.Util;
10 16
11 17
public class EventBaseDTO extends NamedDTO implements IsSerializable {
12 18
private static final long serialVersionUID = 818666323178097939L;
19
+ private static final String STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE = "stage";
13 20
public VenueDTO venue;
14 21
public Date startDate;
15 22
public Date endDate;
... ...
@@ -26,6 +33,11 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
26 33
/** placeholder for social media URL's -> attributes will be implemented later on */
27 34
private String facebookURL;
28 35
private String twitterURL;
36
+ /**
37
+ * For the image URL keys holds the sizes of these images if known. An image size is "known" by this object if it was
38
+ * provided to the {@link #setImageSize} method.
39
+ */
40
+ private Map<String, ImageSize> imageSizes;
29 41
30 42
/**
31 43
* The base URL for the server instance on which the data for this event can be reached. Could be something like
... ...
@@ -45,11 +57,13 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
45 57
46 58
public EventBaseDTO(List<? extends LeaderboardGroupBaseDTO> leaderboardGroups) {
47 59
this.leaderboardGroups = leaderboardGroups;
60
+ this.imageSizes = new HashMap<String, ImageSize>();
48 61
}
49 62
50 63
public EventBaseDTO(String name, List<? extends LeaderboardGroupBaseDTO> leaderboardGroups) {
51 64
super(name);
52 65
this.leaderboardGroups = leaderboardGroups;
66
+ this.imageSizes = new HashMap<String, ImageSize>();
53 67
}
54 68
55 69
public boolean isRunning() {
... ...
@@ -113,21 +127,63 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
113 127
return imageURLs;
114 128
}
115 129
130
+ /**
131
+ * The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back rules:
132
+ * <ol>
133
+ * <li>If one or more image URLs has "stage" (ignoring case) in its name, only they are considered candidates.</li>
134
+ * <li>If no image URL has "stage" (ignoring case) in its name, all images from {@link #imageURLs} are considered candidates.</li>
135
+ * <li>From all candidates, the one with the biggest known size (determined by the product of width and height) is chosen.</li>
136
+ * <li>If the size isn't known for any candidate, the first candidate in {@link #imageURLs} is picked.</li>
137
+ * </ol>
138
+ */
116 139
public String getStageImageURL() {
117
- String result = null;
118
- for(String imageUrl: imageURLs) {
119
- if(imageUrl.contains("stage") || imageUrl.contains("STAGE")) {
120
- result = imageUrl;
121
- break;
122
- }
140
+ final String result;
141
+ if (imageURLs.isEmpty()) {
142
+ result = null;
143
+ } else {
144
+ Comparator<String> stageImageComparator = new Comparator<String>() {
145
+ @Override
146
+ public int compare(String o1, String o2) {
147
+ final int result;
148
+ if (o1.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) {
149
+ if (o2.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) {
150
+ result = compareBySize(o1, o2);
151
+ } else {
152
+ // o1 has stage indicator substring in its URL but o2 doesn't; o1 ranks greater
153
+ result = 1;
154
+ }
155
+ } else {
156
+ if (o2.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) {
157
+ result = -1; // o1 does not and o2 does have stage indicator substring, so o2 ranks greater
158
+ } else {
159
+ // both don't have stage indicator; compare by size
160
+ result = compareBySize(o1, o2);
161
+ }
162
+ }
163
+ return result;
164
+ }
165
+
166
+ private int compareBySize(String o1, String o2) {
167
+ final int result;
168
+ final ImageSize o1Size = getImageSize(o1);
169
+ final ImageSize o2Size = getImageSize(o2);
170
+ result = (o1Size == null ? 0 : (o1Size.getWidth() * o1Size.getHeight()))
171
+ - (o2Size == null ? 0 : (o2Size.getWidth() * o2Size.getHeight()));
172
+ return result;
173
+ }
174
+ };
175
+ List<String> sortedImageURLs = new ArrayList<>(imageURLs);
176
+ Collections.sort(sortedImageURLs, stageImageComparator);
177
+ result = sortedImageURLs.get(sortedImageURLs.size() - 1);
123 178
}
124 179
return result;
125 180
}
126 181
127 182
public List<String> getPhotoGalleryImageURLs() {
183
+ String stageImageURL = getStageImageURL(); // if set, exclude stage image from photo gallery
128 184
List<String> result = new ArrayList<String>();
129
- for(String imageUrl: imageURLs) {
130
- if(!imageUrl.contains("stage") && !imageUrl.contains("STAGE")) {
185
+ for (String imageUrl : imageURLs) {
186
+ if (imageURLs.size() == 1 || !Util.equalsWithNull(imageUrl, stageImageURL)) {
131 187
result.add(imageUrl);
132 188
}
133 189
}
... ...
@@ -169,5 +225,21 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
169 225
public void setTwitterURL(String twitterURL) {
170 226
this.twitterURL = twitterURL;
171 227
}
228
+
229
+ public void setImageSize(String imageURL, ImageSize imageSize) {
230
+ if (imageSize == null) {
231
+ imageSizes.remove(imageURL);
232
+ } else {
233
+ imageSizes.put(imageURL, imageSize);
234
+ }
235
+ }
172 236
237
+ /**
238
+ * @return the size of the image referenced by <code>imageURL</code> or <code>null</code> if that size is not known
239
+ * of the image URL is none of those known to this event, in particular neither of {@link #getImageURLs()}
240
+ * or {@link #getSponsorImageURLs()}.
241
+ */
242
+ public ImageSize getImageSize(String imageURL) {
243
+ return imageSizes.get(imageURL);
244
+ }
173 245
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java
... ...
@@ -36,20 +36,14 @@ public class MarkVectorGraphics {
36 36
this.markWidthInMeters = 1.5;
37 37
}
38 38
39
- public void drawMarkToCanvas(Context2d ctx, boolean isSelected,
40
- double width, double height, double scaleFactor) {
41
-
39
+ public void drawMarkToCanvas(Context2d ctx, boolean isSelected, double width, double height, double scaleFactor) {
42 40
ctx.save();
43 41
ctx.clearRect(0, 0, width, height);
44
-
45 42
ctx.translate(width / 2.0, height / 2.0);
46 43
ctx.scale(scaleFactor, scaleFactor);
47
-
48 44
ctx.translate(-anchorPointX * 100,- anchorPointY * 100);
49
-
50 45
String markColor = color != null ? color : DEFAULT_MARK_COLOR;
51 46
drawMark(ctx, isSelected, markColor);
52
-
53 47
ctx.restore();
54 48
}
55 49
... ...
@@ -61,19 +55,19 @@ public class MarkVectorGraphics {
61 55
if(Shape.CYLINDER.name().equalsIgnoreCase(shape) && pattern != null && Pattern.CHECKERED.name().equalsIgnoreCase(pattern)) {
62 56
drawBuoyWithFinishFlag(ctx, isSelected, color);
63 57
} else if (Shape.CONICAL.name().equalsIgnoreCase(shape)) {
64
- drawConicalBuoy(ctx, isSelected, color);
58
+ drawConicalBuoy(ctx, color);
65 59
} else {
66
- drawSimpleBuoy(ctx, isSelected, color);
60
+ drawSimpleBuoy(ctx, color);
67 61
}
68 62
} else {
69
- drawSimpleBuoy(ctx, isSelected, color);
63
+ drawSimpleBuoy(ctx, color);
70 64
}
71 65
break;
72 66
case FINISHBOAT:
73 67
drawFinishBoat(ctx, isSelected, color);
74 68
break;
75 69
case LANDMARK:
76
- drawLandmark(ctx, isSelected, color);
70
+ drawLandmark(ctx, color);
77 71
break;
78 72
case STARTBOAT:
79 73
drawStartBoat(ctx, isSelected, color);
... ...
@@ -87,7 +81,7 @@ public class MarkVectorGraphics {
87 81
}
88 82
}
89 83
90
- protected void drawSimpleBuoy(Context2d ctx, boolean isSelected, String color) {
84
+ void drawSimpleBuoy(Context2d ctx, String color) {
91 85
ctx.setStrokeStyle("rgba(0,0,0,0)");
92 86
93 87
ctx.save();
... ...
@@ -336,7 +330,7 @@ public class MarkVectorGraphics {
336 330
ctx.restore();
337 331
}
338 332
339
- protected void drawLandmark(Context2d ctx, boolean isSelected, String color) {
333
+ protected void drawLandmark(Context2d ctx, String color) {
340 334
ctx.setStrokeStyle("rgba(0,0,0,0)");
341 335
342 336
ctx.save();
... ...
@@ -401,7 +395,7 @@ public class MarkVectorGraphics {
401 395
ctx.restore();
402 396
}
403 397
404
- protected void drawConicalBuoy(Context2d ctx, boolean isSelected, String color) {
398
+ protected void drawConicalBuoy(Context2d ctx, String color) {
405 399
ctx.setStrokeStyle("rgba(0,0,0,0)");
406 400
407 401
ctx.save();
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathCanvasOverlay.java
... ...
@@ -12,6 +12,7 @@ import com.google.gwt.maps.client.MapWidget;
12 12
import com.google.gwt.maps.client.base.LatLng;
13 13
import com.google.gwt.maps.client.base.Point;
14 14
import com.google.gwt.maps.client.geometrylib.SphericalUtils;
15
+import com.sap.sailing.domain.common.AbstractBearing;
15 16
import com.sap.sailing.domain.common.Mile;
16 17
import com.sap.sailing.domain.common.dto.PositionDTO;
17 18
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
... ...
@@ -164,7 +165,7 @@ public class PathCanvasOverlay extends WindFieldCanvasOverlay implements Named {
164 165
//if ((displayWindAlongPath) && ((index % arrowInterleave) == 0)) {
165 166
if (displayWindAlongPath) {
166 167
if (checkPointsAreFarEnough(windDTO,prevWindDTO)) {
167
- DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg);
168
+ AbstractBearing dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg);
168 169
// System.out.print("index: "+index+"\n");
169 170
170 171
drawScaledArrow(windDTO, dbi.getRadians(), index, true);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/PathLegendCanvasOverlay.java
... ...
@@ -9,6 +9,7 @@ import com.google.gwt.canvas.dom.client.TextMetrics;
9 9
import com.google.gwt.i18n.client.DateTimeFormat;
10 10
import com.google.gwt.i18n.client.TimeZone;
11 11
import com.google.gwt.maps.client.MapWidget;
12
+import com.sap.sailing.domain.common.AbstractBearing;
12 13
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
13 14
import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay;
14 15
import com.sap.sailing.simulator.util.SailingSimulatorConstants;
... ...
@@ -102,7 +103,7 @@ public class PathLegendCanvasOverlay extends FullCanvasOverlay {
102 103
//
103 104
// TODO: draw current arrow
104 105
//
105
- DegreeBearingImpl curBear = new DegreeBearingImpl(this.curBearing);
106
+ AbstractBearing curBear = new DegreeBearingImpl(this.curBearing);
106 107
107 108
//Context2d context2d = canvas.getContext2d();
108 109
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindFieldCanvasOverlay.java
... ...
@@ -14,6 +14,7 @@ import java.util.logging.Logger;
14 14
import com.google.gwt.canvas.dom.client.Context2d;
15 15
import com.google.gwt.event.shared.HandlerRegistration;
16 16
import com.google.gwt.maps.client.MapWidget;
17
+import com.sap.sailing.domain.common.AbstractBearing;
17 18
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
18 19
import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO;
19 20
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
... ...
@@ -149,7 +150,7 @@ public class WindFieldCanvasOverlay extends FullCanvasOverlay implements TimeLis
149 150
while (windDTOIter.hasNext()) {
150 151
final SimulatorWindDTO windDTO = windDTOIter.next();
151 152
//System.out.println("wind angle: "+windDTO.trueWindBearingDeg);
152
- final DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg);
153
+ final AbstractBearing dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg);
153 154
drawScaledArrow(windDTO, dbi.getRadians(), ++index, drawHead);
154 155
}
155 156
final String title = "Wind Field at " + windDTOList.size() + " points.";
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/WindLineGuidesCanvasOverlay.java
... ...
@@ -15,6 +15,7 @@ import com.google.gwt.canvas.dom.client.Context2d;
15 15
import com.google.gwt.maps.client.MapWidget;
16 16
import com.google.gwt.maps.client.base.LatLng;
17 17
import com.google.gwt.maps.client.base.Point;
18
+import com.sap.sailing.domain.common.AbstractBearing;
18 19
import com.sap.sailing.domain.common.dto.PositionDTO;
19 20
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
20 21
import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO;
... ...
@@ -158,7 +159,7 @@ public class WindLineGuidesCanvasOverlay extends FullCanvasOverlay implements Ti
158 159
SimulatorWindDTO windDTO = windDTOIter.next();
159 160
//System.out.println("wind angle: "+index+", "+windDTO.trueWindBearingDeg);
160 161
if (((index % xRes) > 0)&&((index % xRes) < (xRes-1))) {
161
- DegreeBearingImpl dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg);
162
+ AbstractBearing dbi = new DegreeBearingImpl(windDTO.trueWindBearingDeg);
162 163
drawScaledArrow(windDTO, dbi.getRadians(), index, pxLength, drawHead);
163 164
}
164 165
index++;
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/RaceBoard.gwt.xml
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/D35.png
... ...
Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/D35.png differ
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png
java/com.sap.sailing.media.persistence.test/src/com/sap/sailing/media/persistence/test/MediaDBTest.java
... ...
@@ -1,7 +1,6 @@
1 1
package com.sap.sailing.media.persistence.test;
2 2
3 3
import java.net.UnknownHostException;
4
-import java.util.Date;
5 4
import java.util.List;
6 5
7 6
import org.bson.types.ObjectId;
... ...
@@ -9,6 +8,10 @@ import org.hamcrest.core.Is;
9 8
import org.junit.Test;
10 9
11 10
import com.mongodb.MongoException;
11
+import com.sap.sailing.domain.common.Duration;
12
+import com.sap.sailing.domain.common.TimePoint;
13
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
14
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
12 15
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
13 16
import com.sap.sailing.domain.persistence.media.DBMediaTrack;
14 17
import com.sap.sailing.domain.persistence.media.MediaDB;
... ...
@@ -29,10 +32,10 @@ public class MediaDBTest extends AbstractMongoDBTest {
29 32
final String videoTitle = "Test Video";
30 33
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
31 34
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
32
- Date date = new Date();
33
- int durationInMillis = 23;
35
+ TimePoint startTime = MillisecondsTimePoint.now();
36
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
34 37
String mimeType = MimeType.ogv.name();
35
- String dbId = mongoDB.insertMediaTrack(videoTitle, url, date, durationInMillis, mimeType);
38
+ String dbId = mongoDB.insertMediaTrack(videoTitle, url, startTime, duration, mimeType);
36 39
assertNotNull(dbId);
37 40
DBMediaTrack videoTrack = mongoDB.loadAllMediaTracks().iterator().next();
38 41
assertNotNull(videoTrack);
... ...
@@ -46,10 +49,10 @@ public class MediaDBTest extends AbstractMongoDBTest {
46 49
final String videoTitle = "Test Video";
47 50
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
48 51
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
49
- Date date = new Date();
50
- int durationInMillis = 23;
52
+ TimePoint startTime = MillisecondsTimePoint.now();
53
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
51 54
String mimeType = MimeType.ogv.name();
52
- mongoDB.insertMediaTrackWithId(dbId, videoTitle, url, date, durationInMillis, mimeType);
55
+ mongoDB.insertMediaTrackWithId(dbId, videoTitle, url, startTime, duration, mimeType);
53 56
DBMediaTrack videoTrack = mongoDB.loadAllMediaTracks().iterator().next();
54 57
assertNotNull(videoTrack);
55 58
assertThat(videoTrack.dbId, Is.is(dbId));
... ...
@@ -62,10 +65,10 @@ public class MediaDBTest extends AbstractMongoDBTest {
62 65
final String videoTitle = "Test Video";
63 66
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
64 67
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
65
- Date date = new Date();
66
- int durationInMillis = 23;
68
+ TimePoint startTime = MillisecondsTimePoint.now();
69
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
67 70
String mimeType = MimeType.ogv.name();
68
- mongoDB.insertMediaTrackWithId(dbId, videoTitle, url, date, durationInMillis, mimeType);
71
+ mongoDB.insertMediaTrackWithId(dbId, videoTitle, url, startTime, duration, mimeType);
69 72
}
70 73
71 74
@Test(expected=IllegalArgumentException.class)
... ...
@@ -73,11 +76,11 @@ public class MediaDBTest extends AbstractMongoDBTest {
73 76
final String videoTitle = "Test Video";
74 77
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
75 78
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
76
- Date date = new Date();
77
- int durationInMillis = 23;
79
+ TimePoint startTime = MillisecondsTimePoint.now();
80
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
78 81
String mimeType = MimeType.ogv.name();
79
- String dbId = mongoDB.insertMediaTrack(videoTitle, url, date, durationInMillis, mimeType);
80
- mongoDB.insertMediaTrackWithId(dbId, videoTitle, url, date, durationInMillis, mimeType);
82
+ String dbId = mongoDB.insertMediaTrack(videoTitle, url, startTime, duration, mimeType);
83
+ mongoDB.insertMediaTrackWithId(dbId, videoTitle, url, startTime, duration, mimeType);
81 84
}
82 85
83 86
// @Test
... ...
@@ -87,12 +90,12 @@ public class MediaDBTest extends AbstractMongoDBTest {
87 90
// final String title = "Test Video";
88 91
// final String url = "test";
89 92
// Date date = new Date();
90
-// int durationInMillis = 23;
93
+// int duration = 23;
91 94
// String mimeType = MimeType.ogv.name();
92 95
//
93 96
// MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
94 97
//
95
-// mongoDB.importMediaTrack(dbId, title, url, date, durationInMillis, mimeType);
98
+// mongoDB.importMediaTrack(dbId, title, url, date, duration, mimeType);
96 99
//
97 100
// List<DBMediaTrack> allMediaTracks = mongoDB.loadAllMediaTracks();
98 101
// assertThat(allMediaTracks.size(), is(1));
... ...
@@ -101,7 +104,7 @@ public class MediaDBTest extends AbstractMongoDBTest {
101 104
// assertThat(dbMediaTrack.title, is(title));
102 105
// assertThat(dbMediaTrack.url, is(url));
103 106
// assertThat(dbMediaTrack.startTime, is(date));
104
-// assertThat(dbMediaTrack.durationInMillis, is(durationInMillis));
107
+// assertThat(dbMediaTrack.duration, is(duration));
105 108
// assertThat(dbMediaTrack.mimeType, is(mimeType));
106 109
// }
107 110
//
... ...
@@ -112,12 +115,12 @@ public class MediaDBTest extends AbstractMongoDBTest {
112 115
// final String title = "Test Video";
113 116
// final String url = "test";
114 117
// Date date = new Date();
115
-// int durationInMillis = 23;
118
+// int duration = 23;
116 119
// String mimeType = MimeType.ogv.name();
117 120
//
118 121
// MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
119 122
//
120
-// mongoDB.importMediaTrack(dbId, title, url, date, durationInMillis, mimeType);
123
+// mongoDB.importMediaTrack(dbId, title, url, date, duration, mimeType);
121 124
// }
122 125
//
123 126
// @Test
... ...
@@ -126,14 +129,14 @@ public class MediaDBTest extends AbstractMongoDBTest {
126 129
// final String title = "Test Video";
127 130
// final String url = "test";
128 131
// Date date = new Date();
129
-// int durationInMillis = 23;
132
+// int duration = 23;
130 133
// String mimeType = MimeType.ogv.name();
131 134
//
132 135
// MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
133
-// String dbId = mongoDB.insertMediaTrack(title, url, date, durationInMillis, mimeType);
136
+// String dbId = mongoDB.insertMediaTrack(title, url, date, duration, mimeType);
134 137
//
135 138
// String newTitle = title + "x";
136
-// boolean trackCreated = mongoDB.importMediaTrack(dbId, newTitle, url, date, durationInMillis, mimeType);
139
+// boolean trackCreated = mongoDB.importMediaTrack(dbId, newTitle, url, date, duration, mimeType);
137 140
//
138 141
// assertThat(trackCreated, equalTo(true));
139 142
// List<DBMediaTrack> allMediaTracks = mongoDB.loadAllMediaTracks();
... ...
@@ -143,7 +146,7 @@ public class MediaDBTest extends AbstractMongoDBTest {
143 146
// assertThat(dbMediaTrack.title, is(newTitle));
144 147
// assertThat(dbMediaTrack.url, is(url));
145 148
// assertThat(dbMediaTrack.startTime, is(date));
146
-// assertThat(dbMediaTrack.durationInMillis, is(durationInMillis));
149
+// assertThat(dbMediaTrack.duration, is(duration));
147 150
// assertThat(dbMediaTrack.mimeType, is(mimeType));
148 151
// }
149 152
//
... ...
@@ -153,14 +156,14 @@ public class MediaDBTest extends AbstractMongoDBTest {
153 156
// final String title = "Test Video";
154 157
// final String url = "test";
155 158
// Date date = new Date();
156
-// int durationInMillis = 23;
159
+// int duration = 23;
157 160
// String mimeType = MimeType.ogv.name();
158 161
//
159 162
// MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
160
-// String dbId = mongoDB.insertMediaTrack(title, url, date, durationInMillis, mimeType);
163
+// String dbId = mongoDB.insertMediaTrack(title, url, date, duration, mimeType);
161 164
//
162 165
// String newUrl = url + "x";
163
-// boolean trackCreated = mongoDB.importMediaTrack(dbId, title, newUrl, date, durationInMillis, mimeType);
166
+// boolean trackCreated = mongoDB.importMediaTrack(dbId, title, newUrl, date, duration, mimeType);
164 167
//
165 168
// assertThat(trackCreated, equalTo(true));
166 169
// List<DBMediaTrack> allMediaTracks = mongoDB.loadAllMediaTracks();
... ...
@@ -170,7 +173,7 @@ public class MediaDBTest extends AbstractMongoDBTest {
170 173
// assertThat(dbMediaTrack.title, is(title));
171 174
// assertThat(dbMediaTrack.url, is(newUrl));
172 175
// assertThat(dbMediaTrack.startTime, is(date));
173
-// assertThat(dbMediaTrack.durationInMillis, is(durationInMillis));
176
+// assertThat(dbMediaTrack.duration, is(duration));
174 177
// assertThat(dbMediaTrack.mimeType, is(mimeType));
175 178
// }
176 179
//
... ...
@@ -180,14 +183,14 @@ public class MediaDBTest extends AbstractMongoDBTest {
180 183
// final String title = "Test Video";
181 184
// final String url = "test";
182 185
// Date startTime = new Date();
183
-// int durationInMillis = 23;
186
+// int duration = 23;
184 187
// String mimeType = MimeType.ogv.name();
185 188
//
186 189
// MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
187
-// String dbId = mongoDB.insertMediaTrack(title, url, startTime, durationInMillis, mimeType);
190
+// String dbId = mongoDB.insertMediaTrack(title, url, startTime, duration, mimeType);
188 191
//
189 192
// Date newStartTime = new Date(startTime.getTime() + 1);
190
-// boolean trackCreated = mongoDB.importMediaTrack(dbId, title, url, newStartTime, durationInMillis, mimeType);
193
+// boolean trackCreated = mongoDB.importMediaTrack(dbId, title, url, newStartTime, duration, mimeType);
191 194
//
192 195
// assertThat(trackCreated, equalTo(true));
193 196
// List<DBMediaTrack> allMediaTracks = mongoDB.loadAllMediaTracks();
... ...
@@ -197,7 +200,7 @@ public class MediaDBTest extends AbstractMongoDBTest {
197 200
// assertThat(dbMediaTrack.title, is(title));
198 201
// assertThat(dbMediaTrack.url, is(url));
199 202
// assertThat(dbMediaTrack.startTime, is(newStartTime));
200
-// assertThat(dbMediaTrack.durationInMillis, is(durationInMillis));
203
+// assertThat(dbMediaTrack.duration, is(duration));
201 204
// assertThat(dbMediaTrack.mimeType, is(mimeType));
202 205
// }
203 206
//
... ...
@@ -207,13 +210,13 @@ public class MediaDBTest extends AbstractMongoDBTest {
207 210
// final String title = "Test Video";
208 211
// final String url = "test";
209 212
// Date startTime = new Date();
210
-// int durationInMillis = 23;
213
+// int duration = 23;
211 214
// String mimeType = MimeType.ogv.name();
212 215
//
213 216
// MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
214
-// String dbId = mongoDB.insertMediaTrack(title, url, startTime, durationInMillis, mimeType);
217
+// String dbId = mongoDB.insertMediaTrack(title, url, startTime, duration, mimeType);
215 218
//
216
-// int newDurationInMillis = durationInMillis + 1;
219
+// int newDurationInMillis = duration + 1;
217 220
// boolean trackCreated = mongoDB.importMediaTrack(dbId, title, url, startTime, newDurationInMillis, mimeType);
218 221
//
219 222
// assertThat(trackCreated, equalTo(true));
... ...
@@ -224,7 +227,7 @@ public class MediaDBTest extends AbstractMongoDBTest {
224 227
// assertThat(dbMediaTrack.title, is(title));
225 228
// assertThat(dbMediaTrack.url, is(url));
226 229
// assertThat(dbMediaTrack.startTime, is(startTime));
227
-// assertThat(dbMediaTrack.durationInMillis, is(newDurationInMillis));
230
+// assertThat(dbMediaTrack.duration, is(newDurationInMillis));
228 231
// assertThat(dbMediaTrack.mimeType, is(mimeType));
229 232
// }
230 233
//
... ...
@@ -240,12 +243,12 @@ public class MediaDBTest extends AbstractMongoDBTest {
240 243
private void storeNumberOfTestMediaTracks(MediaDB mongoDB, int count) {
241 244
final String videoTitleTemplate = "Test Video ";
242 245
final String url = "http://localhost:8888/media/HTML5/1809147112001_1842870496001_SAP-Regatta-Day02-Final_libtheora.ogv";
243
- Date date = new Date();
244
- int durationInMillis = 23;
246
+ TimePoint startTime = MillisecondsTimePoint.now();
247
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
245 248
String mimeType = MimeType.ogv.name();
246 249
247 250
for (int i = 0; i < count; i++) {
248
- mongoDB.insertMediaTrack(videoTitleTemplate + i, url, date, durationInMillis, mimeType);
251
+ mongoDB.insertMediaTrack(videoTitleTemplate + i, url, startTime, duration, mimeType);
249 252
}
250 253
}
251 254
... ...
@@ -255,10 +258,10 @@ public class MediaDBTest extends AbstractMongoDBTest {
255 258
final String videoTitle = "Test Video";
256 259
final String url = "test";
257 260
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
258
- Date date = new Date();
259
- int durationInMillis = 23;
261
+ TimePoint startTime = MillisecondsTimePoint.now();
262
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
260 263
String mimeType = MimeType.ogv.name();
261
- String dbId = mongoDB.insertMediaTrack(videoTitle, url, date, durationInMillis, mimeType);
264
+ String dbId = mongoDB.insertMediaTrack(videoTitle, url, startTime, duration, mimeType);
262 265
263 266
//delete
264 267
mongoDB.deleteMediaTrack(dbId);
... ...
@@ -273,19 +276,19 @@ public class MediaDBTest extends AbstractMongoDBTest {
273 276
final String videoTitle = "Test Video";
274 277
final String url = "test";
275 278
MediaDB mongoDB = MediaDBFactory.INSTANCE.getMediaDB(getMongoService());
276
- Date originalDate = new Date();
277
- int durationInMillis = 23;
279
+ TimePoint originalDate = MillisecondsTimePoint.now();
280
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
278 281
String mimeType = MimeType.ogv.name();
279
- String dbId = mongoDB.insertMediaTrack(videoTitle, url, originalDate, durationInMillis, mimeType);
282
+ String dbId = mongoDB.insertMediaTrack(videoTitle, url, originalDate, duration, mimeType);
280 283
281 284
//update with new date
282
- Date newDate = new Date(originalDate.getTime() + 1000);
285
+ TimePoint newDate = originalDate.plus(1000);
283 286
mongoDB.updateStartTime(dbId, newDate);
284 287
285 288
//assert start time is updated and everything else preserved
286 289
DBMediaTrack videoTrack = mongoDB.loadAllMediaTracks().iterator().next();
287 290
assertThat(videoTrack.startTime, is(newDate));
288
- assertThat(videoTrack.durationInMillis, is(durationInMillis));
291
+ assertThat(videoTrack.duration, is(duration));
289 292
assertThat(videoTrack.title, is(videoTitle));
290 293
assertThat(videoTrack.url, is(url));
291 294
assertThat(videoTrack.mimeType, is(mimeType));
java/com.sap.sailing.operationaltransformation/.classpath
... ...
@@ -1,7 +1,7 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<classpath>
3
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
4
- <classpathentry kind="src" path="src"/>
5
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
6
- <classpathentry kind="output" path="bin"/>
7
-</classpath>
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<classpath>
3
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
4
+ <classpathentry kind="src" path="src"/>
5
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
6
+ <classpathentry kind="output" path="bin"/>
7
+</classpath>
java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java
... ...
@@ -12,8 +12,8 @@ import org.json.simple.JSONObject;
12 12
import com.sap.sailing.domain.base.EventBase;
13 13
import com.sap.sailing.domain.base.LeaderboardGroupBase;
14 14
import com.sap.sailing.domain.base.Venue;
15
-import com.sap.sailing.domain.base.impl.EventBaseImpl;
16 15
import com.sap.sailing.domain.base.impl.StrippedEventImpl;
16
+import com.sap.sailing.domain.common.impl.ImageSizeImpl;
17 17
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
18 18
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
19 19
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
... ...
@@ -50,7 +50,7 @@ public class EventBaseJsonDeserializer implements JsonDeserializer<EventBase> {
50 50
leaderboardGroups.add(leaderboardGroupDeserializer.deserialize((JSONObject) lgJson));
51 51
}
52 52
}
53
- EventBaseImpl result = new StrippedEventImpl(name, startDate == null ? null : new MillisecondsTimePoint(startDate.longValue()),
53
+ StrippedEventImpl result = new StrippedEventImpl(name, startDate == null ? null : new MillisecondsTimePoint(startDate.longValue()),
54 54
endDate == null ? null : new MillisecondsTimePoint(endDate.longValue()), venue, /* is public */ true, id, leaderboardGroups);
55 55
result.setDescription(description);
56 56
if (officialWebsiteURLAsString != null) {
... ...
@@ -88,6 +88,21 @@ public class EventBaseJsonDeserializer implements JsonDeserializer<EventBase> {
88 88
throw new JsonDeserializationException("Error deserializing sponsor image URLs for event "+name, e);
89 89
}
90 90
}
91
+ JSONArray imageSizes = (JSONArray) object.get(EventBaseJsonSerializer.FIELD_IMAGE_SIZES);
92
+ if (imageSizes != null) {
93
+ for (Object imageURLAndSizeObject : imageSizes) {
94
+ JSONObject imageURLAndSizeJson = (JSONObject) imageURLAndSizeObject;
95
+ try {
96
+ result.setImageSize(
97
+ new URL((String) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_URL)),
98
+ new ImageSizeImpl(
99
+ ((Number) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_WIDTH)).intValue(),
100
+ ((Number) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_HEIGHT)).intValue()));
101
+ } catch (MalformedURLException e) {
102
+ throw new JsonDeserializationException(e);
103
+ }
104
+ }
105
+ }
91 106
return result;
92 107
}
93 108
java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java
... ...
@@ -1,6 +1,9 @@
1 1
package com.sap.sailing.server.gateway.serialization.impl;
2 2
3 3
import java.net.URL;
4
+import java.util.concurrent.ExecutionException;
5
+import java.util.logging.Level;
6
+import java.util.logging.Logger;
4 7
5 8
import org.json.simple.JSONArray;
6 9
import org.json.simple.JSONObject;
... ...
@@ -8,9 +11,12 @@ import org.json.simple.JSONObject;
8 11
import com.sap.sailing.domain.base.EventBase;
9 12
import com.sap.sailing.domain.base.LeaderboardGroupBase;
10 13
import com.sap.sailing.domain.base.Venue;
14
+import com.sap.sailing.domain.common.ImageSize;
11 15
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
12 16
13 17
public class EventBaseJsonSerializer implements JsonSerializer<EventBase> {
18
+ private static final Logger logger = Logger.getLogger(EventBaseJsonSerializer.class.getName());
19
+
14 20
public static final String FIELD_ID = "id";
15 21
public static final String FIELD_NAME = "name";
16 22
public static final String FIELD_DESCRIPTION = "description";
... ...
@@ -21,6 +27,10 @@ public class EventBaseJsonSerializer implements JsonSerializer<EventBase> {
21 27
public static final String FIELD_VIDEO_URLS = "videoURLs";
22 28
public static final String FIELD_SPONSOR_IMAGE_URLS = "sponsorImageURLs";
23 29
public static final String FIELD_LOGO_IMAGE_URL = "logoImageURL";
30
+ public static final String FIELD_IMAGE_SIZES = "imageSizes";
31
+ public static final String FIELD_IMAGE_URL = "imageURL";
32
+ public static final String FIELD_IMAGE_WIDTH = "imageWidth";
33
+ public static final String FIELD_IMAGE_HEIGHT = "imageHeight";
24 34
public static final String FIELD_OFFICIAL_WEBSITE_URL = "officialWebsiteURL";
25 35
public static final String FIELDS_LEADERBOARD_GROUPS = "leaderboardGroups";
26 36
... ...
@@ -50,9 +60,42 @@ public class EventBaseJsonSerializer implements JsonSerializer<EventBase> {
50 60
for (LeaderboardGroupBase lg : event.getLeaderboardGroups()) {
51 61
leaderboardGroups.add(leaderboardGroupBaseSerializer.serialize(lg));
52 62
}
63
+ JSONArray imageSizes = new JSONArray();
64
+ result.put(FIELD_IMAGE_SIZES, imageSizes);
65
+ for (URL imageURL : event.getImageURLs()) {
66
+ addImageSize(imageURL, imageSizes, event);
67
+ }
68
+ if (event.getLogoImageURL() != null) {
69
+ addImageSize(event.getLogoImageURL(), imageSizes, event);
70
+ }
71
+ for (URL sponsorImageURL : event.getSponsorImageURLs()) {
72
+ addImageSize(sponsorImageURL, imageSizes, event);
73
+ }
53 74
return result;
54 75
}
55 76
77
+ /**
78
+ * If <code>eventBase</code> knows the size of the image with URL <code>imageURL</code>, that size is serialized as
79
+ * JSON object to <code>imageSizes</code>
80
+ */
81
+ private void addImageSize(URL imageURL, JSONArray imageSizes, EventBase eventBase) {
82
+ ImageSize imageSize;
83
+ try {
84
+ imageSize = eventBase.getImageSize(imageURL);
85
+ if (imageSize != null) {
86
+ JSONObject imageSizeJson = new JSONObject();
87
+ imageSizes.add(imageSizeJson);
88
+ imageSizeJson.put(FIELD_IMAGE_URL, imageURL.toString());
89
+ imageSizeJson.put(FIELD_IMAGE_WIDTH, imageSize.getWidth());
90
+ imageSizeJson.put(FIELD_IMAGE_HEIGHT, imageSize.getHeight());
91
+ }
92
+ } catch (InterruptedException e) {
93
+ logger.log(Level.FINE, "Couldn't retrieve image size for URL "+imageURL, e);
94
+ } catch (ExecutionException e) {
95
+ logger.log(Level.FINE, "Couldn't retrieve image size for URL "+imageURL, e);
96
+ }
97
+ }
98
+
56 99
private JSONArray getURLsAsStringArray(Iterable<URL> urls) {
57 100
JSONArray jsonImageURLs = new JSONArray();
58 101
for (URL url : urls) {
java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java
... ...
@@ -1,5 +1,8 @@
1 1
package com.sap.sailing.server.gateway.jaxrs;
2 2
3
+import java.math.BigDecimal;
4
+import java.math.RoundingMode;
5
+
3 6
import javax.servlet.ServletContext;
4 7
import javax.ws.rs.core.Context;
5 8
... ...
@@ -63,4 +66,10 @@ public abstract class AbstractSailingServerResource {
63 66
}
64 67
return trackedRace;
65 68
}
69
+
70
+ protected static Double roundDouble(Double value, int places) {
71
+ BigDecimal bigDecimal = new BigDecimal(value);
72
+ bigDecimal = bigDecimal.setScale(places, RoundingMode.HALF_UP);
73
+ return bigDecimal.doubleValue();
74
+ }
66 75
}
java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java
... ...
@@ -667,5 +667,85 @@ public class RegattasResource extends AbstractSailingServerResource {
667 667
return response;
668 668
}
669 669
670
+ @GET
671
+ @Produces("application/json;charset=UTF-8")
672
+ @Path("{regattaname}/races/{racename}/competitors/live")
673
+ public Response getCompetitorLiveRanks(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName,
674
+ @DefaultValue("-1") @QueryParam("topN") Integer topN) {
675
+ Response response;
676
+ Regatta regatta = findRegattaByName(regattaName);
677
+ if (regatta == null) {
678
+ response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
679
+ } else {
680
+ RaceDefinition race = findRaceByName(regatta, raceName);
681
+ if (race == null) {
682
+ response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build();
683
+ } else {
684
+ TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
685
+ Course course = trackedRace.getRace().getCourse();
686
+ TimePoint timePoint = trackedRace.getTimePointOfNewestEvent() == null ? MillisecondsTimePoint.now()
687
+ : trackedRace.getTimePointOfNewestEvent();
688
+ // if(trackedRace.isLive(timePoint)) {
689
+
690
+ JSONObject jsonLiveData = new JSONObject();
691
+ jsonLiveData.put("name", trackedRace.getRace().getName());
692
+ jsonLiveData.put("regatta", regatta.getName());
693
+
694
+ JSONArray jsonCompetitors = new JSONArray();
695
+ try {
696
+ List<Competitor> competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(timePoint);
697
+ Integer rank = 1;
698
+ for (Competitor competitor : competitorsFromBestToWorst) {
699
+ JSONObject jsonCompetitorInLeg = new JSONObject();
700
+
701
+ if(topN != null && topN > 0 && rank > topN) {
702
+ break;
703
+ }
704
+ TrackedLegOfCompetitor currentLegOfCompetitor = trackedRace.getCurrentLeg(competitor, timePoint);
705
+ if (currentLegOfCompetitor != null) {
706
+ jsonCompetitorInLeg.put("id", competitor.getId() != null ? competitor.getId().toString() : null);
707
+ jsonCompetitorInLeg.put("name", competitor.getName());
708
+ jsonCompetitorInLeg.put("sailNumber", competitor.getBoat().getSailID());
709
+ jsonCompetitorInLeg.put("color", competitor.getColor() != null ? competitor.getColor().getAsHtml() : null);
710
+
711
+ jsonCompetitorInLeg.put("rank", rank++);
712
+
713
+ int indexOfWaypoint = course.getIndexOfWaypoint(currentLegOfCompetitor.getLeg().getFrom());
714
+ jsonCompetitorInLeg.put("leg", indexOfWaypoint);
715
+
716
+ Speed speedOverGround = currentLegOfCompetitor.getSpeedOverGround(timePoint);
717
+ if(speedOverGround != null) {
718
+ jsonCompetitorInLeg.put("speedOverGround-kts", roundDouble(speedOverGround.getKnots(), 2));
719
+ }
720
+
721
+ Distance distanceTraveled = currentLegOfCompetitor.getDistanceTraveled(timePoint);
722
+ if (distanceTraveled != null) {
723
+ jsonCompetitorInLeg.put("distanceTraveled-m", roundDouble(distanceTraveled.getMeters(), 2));
724
+ }
725
+
726
+ Double gapToLeaderInSeconds = currentLegOfCompetitor.getGapToLeaderInSeconds(timePoint, WindPositionMode.LEG_MIDDLE);
727
+ if(gapToLeaderInSeconds != null) {
728
+ jsonCompetitorInLeg.put("gapToLeader-s", roundDouble(gapToLeaderInSeconds, 2));
729
+ }
730
+
731
+ Distance windwardDistanceToOverallLeader = currentLegOfCompetitor.getWindwardDistanceToOverallLeader(timePoint, WindPositionMode.LEG_MIDDLE);
732
+ if(windwardDistanceToOverallLeader != null) {
733
+ jsonCompetitorInLeg.put("gapToLeader-m", roundDouble(windwardDistanceToOverallLeader.getMeters(), 2));
734
+ }
735
+
736
+ jsonCompetitors.add(jsonCompetitorInLeg);
737
+ }
738
+ }
739
+ } catch (NoWindException e1) {
740
+ // well, we don't know the wind direction... then no gap to leader will be shown...
741
+ }
742
+ jsonLiveData.put("competitors", jsonCompetitors);
743
+
744
+ String json = jsonLiveData.toJSONString();
745
+ return Response.ok(json, MediaType.APPLICATION_JSON).build();
746
+ }
747
+ }
748
+ return response;
749
+ }
670 750
}
671 751
... ...
\ No newline at end of file
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java
... ...
@@ -122,7 +122,7 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe
122 122
List<String> regattas = new ArrayList<String>();
123 123
regattas.add("Day1");
124 124
regattas.add("Day2");
125
- return master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
125
+ return master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
126 126
}
127 127
128 128
private void stopMessagingExchange() {
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/EventReplicationTest.java
... ...
@@ -35,7 +35,7 @@ public class EventReplicationTest extends AbstractServerReplicationTest {
35 35
List<String> regattas = new ArrayList<String>();
36 36
regattas.add("Day1");
37 37
regattas.add("Day2");
38
- Event masterEvent = master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
38
+ Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
39 39
40 40
Thread.sleep(1000);
41 41
Event replicatedEvent = replica.getEvent(masterEvent.getId());
... ...
@@ -54,7 +54,7 @@ public class EventReplicationTest extends AbstractServerReplicationTest {
54 54
List<String> regattas = new ArrayList<String>();
55 55
regattas.add("Day1");
56 56
regattas.add("Day2");
57
- final Event masterEvent = master.addEvent(eventName, null, null, venueName, isPublic, UUID.randomUUID());
57
+ final Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, null, null, venueName, isPublic, UUID.randomUUID());
58 58
final String leaderboardGroupName = "LGName";
59 59
LeaderboardGroup lg = master.apply(new CreateLeaderboardGroup(leaderboardGroupName, "LGDescription", /* displayGroupsInReverseOrder */
60 60
"displayName", /* leaderboardNames */
... ...
@@ -78,7 +78,7 @@ public class EventReplicationTest extends AbstractServerReplicationTest {
78 78
List<String> regattas = new ArrayList<String>();
79 79
regattas.add("Day1");
80 80
regattas.add("Day2");
81
- Event masterEvent = master.addEvent(eventName, null, null, venueName, isPublic, UUID.randomUUID());
81
+ Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, null, null, venueName, isPublic, UUID.randomUUID());
82 82
83 83
Thread.sleep(1000);
84 84
Event replicatedEvent = replica.getEvent(masterEvent.getId());
... ...
@@ -97,7 +97,7 @@ public class EventReplicationTest extends AbstractServerReplicationTest {
97 97
final String courseArea = "Alpha";
98 98
final TimePoint eventStartDate = new MillisecondsTimePoint(new Date());
99 99
final TimePoint eventEndDate = new MillisecondsTimePoint(new Date());
100
- Event masterEvent = master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
100
+ Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
101 101
CourseArea masterCourseArea = master.addCourseArea(masterEvent.getId(), courseArea, UUID.randomUUID());
102 102
103 103
Thread.sleep(1000);
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java
... ...
@@ -1,6 +1,7 @@
1 1
package com.sap.sailing.server.replication.test;
2 2
3 3
import static org.hamcrest.core.Is.is;
4
+
4 5
import static org.junit.Assert.assertNotNull;
5 6
import static org.junit.Assert.assertNull;
6 7
import static org.junit.Assert.assertSame;
... ...
@@ -30,6 +31,7 @@ import com.sap.sailing.domain.base.impl.RegattaImpl;
30 31
import com.sap.sailing.domain.common.RegattaName;
31 32
import com.sap.sailing.domain.common.ScoringSchemeType;
32 33
import com.sap.sailing.domain.common.TimePoint;
34
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
33 35
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
34 36
import com.sap.sailing.domain.common.media.MediaTrack;
35 37
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
... ...
@@ -86,7 +88,7 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl
86 88
final TimePoint eventStartDate = new MillisecondsTimePoint(new Date());
87 89
final TimePoint eventEndDate = new MillisecondsTimePoint(new Date());
88 90
89
- Event event = master.addEvent(eventName, eventStartDate, eventEndDate, venue, false, eventId);
91
+ Event event = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venue, false, eventId);
90 92
assertNotNull(master.getEvent(eventId));
91 93
assertNull(replica.getEvent(eventId));
92 94
... ...
@@ -123,11 +125,11 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl
123 125
event.addLeaderboardGroup(leaderboardGroup);
124 126
125 127
/* Media Library */
126
- MediaTrack mediaTrack1 = new MediaTrack("title-1", "url", new Date(), 1, MimeType.mp4);
128
+ MediaTrack mediaTrack1 = new MediaTrack("title-1", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, MimeType.mp4);
127 129
master.mediaTrackAdded(mediaTrack1);
128
- MediaTrack mediaTrack2 = new MediaTrack("title-2", "url", new Date(), 1, MimeType.ogv);
130
+ MediaTrack mediaTrack2 = new MediaTrack("title-2", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, MimeType.ogv);
129 131
master.mediaTrackAdded(mediaTrack2);
130
- MediaTrack mediaTrack3 = new MediaTrack("title-3", "url", new Date(), 1, MimeType.mp4);
132
+ MediaTrack mediaTrack3 = new MediaTrack("title-3", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, MimeType.mp4);
131 133
master.mediaTrackAdded(mediaTrack3);
132 134
133 135
/* fire up replication */
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/MediaReplicationTest.java
... ...
@@ -1,17 +1,18 @@
1 1
package com.sap.sailing.server.replication.test;
2 2
3
-import static org.hamcrest.core.Is.is;
4
-import static org.junit.Assert.assertEquals;
5
-import static org.junit.Assert.assertNotSame;
6
-import static org.junit.Assert.assertThat;
7
-
8
-import java.util.Date;
9
-
10 3
import org.junit.Test;
11 4
5
+import com.sap.sailing.domain.common.Duration;
6
+import com.sap.sailing.domain.common.TimePoint;
7
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
8
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
12 9
import com.sap.sailing.domain.common.media.MediaTrack;
13 10
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
14 11
12
+import static org.junit.Assert.*;
13
+
14
+import static org.hamcrest.core.Is.*;
15
+
15 16
public class MediaReplicationTest extends AbstractServerReplicationTest {
16 17
17 18
private void waitSomeTime() throws InterruptedException {
... ...
@@ -22,10 +23,10 @@ public class MediaReplicationTest extends AbstractServerReplicationTest {
22 23
private MediaTrack createMediaTrack() {
23 24
String title = "title";
24 25
String url = "url";
25
- Date startTime = new Date();
26
- int durationInMillis = 1;
26
+ TimePoint startTime = MillisecondsTimePoint.now();
27
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
27 28
MimeType mimeType = MimeType.mp4;
28
- MediaTrack mediaTrack = new MediaTrack(title, url, startTime, durationInMillis, mimeType);
29
+ MediaTrack mediaTrack = new MediaTrack(title, url, startTime, duration, mimeType);
29 30
return mediaTrack;
30 31
}
31 32
... ...
@@ -81,7 +82,7 @@ public class MediaReplicationTest extends AbstractServerReplicationTest {
81 82
public void testUpdateMediaTrackStartTimeReplication() throws InterruptedException {
82 83
MediaTrack mediaTrack = createMediaTrack();
83 84
master.mediaTrackAdded(mediaTrack);
84
- mediaTrack.startTime = new Date(mediaTrack.startTime.getTime() + 1000);
85
+ mediaTrack.startTime = mediaTrack.startTime.plus(1000);
85 86
master.mediaTrackStartTimeChanged(mediaTrack);
86 87
waitSomeTime();
87 88
assertThat(replica.getAllMediaTracks().size(), is(1));
... ...
@@ -92,11 +93,11 @@ public class MediaReplicationTest extends AbstractServerReplicationTest {
92 93
public void testUpdateMediaTrackDurationReplication() throws InterruptedException {
93 94
MediaTrack mediaTrack = createMediaTrack();
94 95
master.mediaTrackAdded(mediaTrack);
95
- mediaTrack.durationInMillis = mediaTrack.durationInMillis + 1000;
96
+ mediaTrack.duration = mediaTrack.duration.plus(1000);
96 97
master.mediaTrackDurationChanged(mediaTrack);
97 98
waitSomeTime();
98 99
assertThat(replica.getAllMediaTracks().size(), is(1));
99
- assertThat(replica.getAllMediaTracks().iterator().next().durationInMillis, is(mediaTrack.durationInMillis));
100
+ assertThat(replica.getAllMediaTracks().iterator().next().duration, is(mediaTrack.duration));
100 101
}
101 102
102 103
}
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java
... ...
@@ -66,7 +66,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest {
66 66
final TimePoint eventStartDate = new MillisecondsTimePoint(new Date());
67 67
final TimePoint eventEndDate = new MillisecondsTimePoint(new Date());
68 68
69
- Event event = master.addEvent("Event", eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID());
69
+ Event event = master.addEvent("Event", /* eventDescription */ null, eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID());
70 70
master.addCourseArea(event.getId(), "Alpha", alphaCourseAreaId);
71 71
master.addCourseArea(event.getId(), "TV", tvCourseAreaId);
72 72
... ...
@@ -112,7 +112,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest {
112 112
final UUID golfCourseAreaId = UUID.randomUUID();
113 113
final TimePoint eventStartDate = new MillisecondsTimePoint(new Date());
114 114
final TimePoint eventEndDate = new MillisecondsTimePoint(new Date());
115
- Event event = master.addEvent("Event", eventStartDate, eventEndDate, "Venue", /*isPublic*/true, UUID.randomUUID());
115
+ Event event = master.addEvent("Event", /* eventDescription */ null, eventStartDate, eventEndDate, "Venue", /*isPublic*/true, UUID.randomUUID());
116 116
master.addCourseArea(event.getId(), "TV", tvCourseAreaId);
117 117
master.addCourseArea(event.getId(), "Golf", golfCourseAreaId);
118 118
final String regattaName = RegattaImpl.getDefaultName("Kiel Week 2012", "49er");
... ...
@@ -137,7 +137,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest {
137 137
final TimePoint eventStartDate = new MillisecondsTimePoint(new Date());
138 138
final TimePoint eventEndDate = new MillisecondsTimePoint(new Date());
139 139
140
- Event event = master.addEvent("Event", eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID());
140
+ Event event = master.addEvent("Event", /* eventDescription */ null, eventStartDate, eventEndDate, "Venue", true, UUID.randomUUID());
141 141
master.addCourseArea(event.getId(), "Alpha", alphaCourseAreaId);
142 142
143 143
UUID currentCourseAreaId = null;
... ...
@@ -295,7 +295,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest {
295 295
final String courseArea = "Alpha";
296 296
final TimePoint eventStartDate = new MillisecondsTimePoint(new Date());
297 297
final TimePoint eventEndDate = new MillisecondsTimePoint(new Date());
298
- Event masterEvent = master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
298
+ Event masterEvent = master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
299 299
CourseArea masterCourseArea = master.addCourseArea(masterEvent.getId(), courseArea, UUID.randomUUID());
300 300
301 301
Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(eventName, boatClassName), boatClassName, UUID.randomUUID(), series,
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ReplicationStopTest.java
... ...
@@ -38,7 +38,7 @@ public class ReplicationStopTest extends AbstractServerReplicationTest {
38 38
List<String> regattas = new ArrayList<String>();
39 39
regattas.add("Day1");
40 40
regattas.add("Day2");
41
- return master.addEvent(eventName, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
41
+ return master.addEvent(eventName, /* eventDescription */ null, eventStartDate, eventEndDate, venueName, isPublic, UUID.randomUUID());
42 42
}
43 43
44 44
java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MasterDataImporterMediaTest.java
... ...
@@ -3,7 +3,9 @@ package com.sap.sailing.server.impl;
3 3
import static org.hamcrest.CoreMatchers.is;
4 4
import static org.hamcrest.CoreMatchers.not;
5 5
import static org.hamcrest.CoreMatchers.sameInstance;
6
+
6 7
import static org.junit.Assert.assertThat;
8
+
7 9
import static org.mockito.Matchers.any;
8 10
import static org.mockito.Matchers.eq;
9 11
import static org.mockito.Matchers.same;
... ...
@@ -16,13 +18,14 @@ import static org.mockito.Mockito.when;
16 18
import java.util.ArrayList;
17 19
import java.util.Arrays;
18 20
import java.util.Collection;
19
-import java.util.Date;
20 21
21 22
import org.bson.types.ObjectId;
22 23
import org.junit.Before;
23 24
import org.junit.Test;
24 25
import org.mockito.Mockito;
25 26
27
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
28
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
26 29
import com.sap.sailing.domain.common.media.MediaTrack;
27 30
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
28 31
import com.sap.sailing.domain.persistence.DomainObjectFactory;
... ...
@@ -89,7 +92,7 @@ public class MasterDataImporterMediaTest {
89 92
createRacingEventService();
90 93
91 94
String dbId = new ObjectId().toStringMongod();
92
- MediaTrack mediaTrackToImport = new MediaTrack(dbId, "title", "url", new Date(), 0, MimeType.mp3);
95
+ MediaTrack mediaTrackToImport = new MediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, MimeType.mp3);
93 96
mediaTracksToImport.add(mediaTrackToImport);
94 97
racingEventService.mediaTracksImported(mediaTracksToImport, OVERRIDE);
95 98
... ...
@@ -111,11 +114,11 @@ public class MasterDataImporterMediaTest {
111 114
public void testImportOneTrackToSameTarget_WithOverride() throws Exception {
112 115
String dbId = new ObjectId().toStringMongod();
113 116
MimeType mimeType = MimeType.mp3;
114
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
117
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
115 118
createRacingEventService(existingMediaTrack);
116 119
Collection<MediaTrack> allMediaTracksBeforeImport = racingEventService.getAllMediaTracks();
117 120
118
- MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.durationInMillis, mimeType);
121
+ MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.duration, mimeType);
119 122
mediaTracksToImport.add(mediaTrackToImport);
120 123
racingEventService.mediaTracksImported(mediaTracksToImport, OVERRIDE);
121 124
... ...
@@ -134,11 +137,11 @@ public class MasterDataImporterMediaTest {
134 137
public void testImportOneTrackToExistingOtherTrack_WithOverride() throws Exception {
135 138
String dbId = new ObjectId().toStringMongod();
136 139
MimeType mimeType = MimeType.mp3;
137
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
140
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
138 141
createRacingEventService(existingMediaTrack);
139 142
140 143
String dbId2 = new ObjectId().toStringMongod();
141
- MediaTrack mediaTrackToImport = new MediaTrack(dbId2, existingMediaTrack.title, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.durationInMillis, mimeType);
144
+ MediaTrack mediaTrackToImport = new MediaTrack(dbId2, existingMediaTrack.title, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.duration, mimeType);
142 145
mediaTracksToImport.add(mediaTrackToImport);
143 146
racingEventService.mediaTracksImported(mediaTracksToImport, OVERRIDE);
144 147
... ...
@@ -158,10 +161,10 @@ public class MasterDataImporterMediaTest {
158 161
public void testImportOneTrackToSameTargetWithChangedTitle_WithOverride() throws Exception {
159 162
String dbId = new ObjectId().toStringMongod();
160 163
MimeType mimeType = MimeType.mp3;
161
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
164
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
162 165
createRacingEventService(existingMediaTrack);
163 166
164
- MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title + "x", existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.durationInMillis, mimeType);
167
+ MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title + "x", existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.duration, mimeType);
165 168
assertThat(existingMediaTrack.title, is(not(mediaTrackToImport.title)));
166 169
167 170
mediaTracksToImport.add(mediaTrackToImport);
... ...
@@ -185,10 +188,10 @@ public class MasterDataImporterMediaTest {
185 188
public void testImportOneTrackWithNullTitleToSameTargetWithTitle_WithOverride() throws Exception {
186 189
String dbId = new ObjectId().toStringMongod();
187 190
MimeType mimeType = MimeType.mp3;
188
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
191
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
189 192
createRacingEventService(existingMediaTrack);
190 193
191
- MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, null, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.durationInMillis, mimeType);
194
+ MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, null, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.duration, mimeType);
192 195
assertThat(existingMediaTrack.title, is(not(mediaTrackToImport.title)));
193 196
194 197
mediaTracksToImport.add(mediaTrackToImport);
... ...
@@ -211,10 +214,10 @@ public class MasterDataImporterMediaTest {
211 214
public void testImportOneTrackToSameTargetWithChangedTitle_NoOverride() throws Exception {
212 215
String dbId = new ObjectId().toStringMongod();
213 216
MimeType mimeType = MimeType.mp3;
214
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
217
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
215 218
createRacingEventService(existingMediaTrack);
216 219
217
- MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title + "x", existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.durationInMillis, mimeType);
220
+ MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title + "x", existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.duration, mimeType);
218 221
assertThat(existingMediaTrack.title, is(not(mediaTrackToImport.title)));
219 222
220 223
mediaTracksToImport.add(mediaTrackToImport);
... ...
@@ -237,10 +240,10 @@ public class MasterDataImporterMediaTest {
237 240
public void testImportOneTrackToSameTargetWithChangedUrl_WithOverride() throws Exception {
238 241
String dbId = new ObjectId().toStringMongod();
239 242
MimeType mimeType = MimeType.mp3;
240
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
243
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
241 244
createRacingEventService(existingMediaTrack);
242 245
243
- MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url + "x", existingMediaTrack.startTime, existingMediaTrack.durationInMillis, mimeType);
246
+ MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url + "x", existingMediaTrack.startTime, existingMediaTrack.duration, mimeType);
244 247
assertThat(existingMediaTrack.url, is(not(mediaTrackToImport.url)));
245 248
246 249
mediaTracksToImport.add(mediaTrackToImport);
... ...
@@ -263,10 +266,10 @@ public class MasterDataImporterMediaTest {
263 266
public void testImportOneTrackToSameTargetWithChangedStarttime_WithOverride() throws Exception {
264 267
String dbId = new ObjectId().toStringMongod();
265 268
MimeType mimeType = MimeType.mp3;
266
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
269
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
267 270
createRacingEventService(existingMediaTrack);
268 271
269
- MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url, new Date(existingMediaTrack.startTime.getTime() + 1), existingMediaTrack.durationInMillis, mimeType);
272
+ MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url, existingMediaTrack.startTime.plus(1), existingMediaTrack.duration, mimeType);
270 273
assertThat(existingMediaTrack.startTime, is(not(mediaTrackToImport.startTime)));
271 274
272 275
mediaTracksToImport.add(mediaTrackToImport);
... ...
@@ -289,11 +292,11 @@ public class MasterDataImporterMediaTest {
289 292
public void testImportOneTrackToSameTargetWithChangedDuration_WithOverride() throws Exception {
290 293
String dbId = new ObjectId().toStringMongod();
291 294
MimeType mimeType = MimeType.mp3;
292
- DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", new Date(), 0, mimeType.name());
295
+ DBMediaTrack existingMediaTrack = new DBMediaTrack(dbId, "title", "url", MillisecondsTimePoint.now(), MillisecondsDurationImpl.ONE_HOUR, mimeType.name());
293 296
createRacingEventService(existingMediaTrack);
294 297
295
- MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.durationInMillis + 1, mimeType);
296
- assertThat(existingMediaTrack.durationInMillis, is(not(mediaTrackToImport.durationInMillis)));
298
+ MediaTrack mediaTrackToImport = new MediaTrack(existingMediaTrack.dbId, existingMediaTrack.title, existingMediaTrack.url, existingMediaTrack.startTime, existingMediaTrack.duration.plus(1), mimeType);
299
+ assertThat(existingMediaTrack.duration, is(not(mediaTrackToImport.duration)));
297 300
298 301
mediaTracksToImport.add(mediaTrackToImport);
299 302
racingEventService.mediaTracksImported(mediaTracksToImport, OVERRIDE);
... ...
@@ -301,7 +304,7 @@ public class MasterDataImporterMediaTest {
301 304
Collection<MediaTrack> allMediaTracksAfterImport = racingEventService.getAllMediaTracks();
302 305
assertThat(allMediaTracksAfterImport.size(), is(1));
303 306
MediaTrack mediaTrack = allMediaTracksAfterImport.iterator().next();
304
- assertThat(mediaTrack.durationInMillis, is(mediaTrackToImport.durationInMillis));
307
+ assertThat(mediaTrack.duration, is(mediaTrackToImport.duration));
305 308
306 309
verify(racingEventService, never()).mediaTrackAdded(any(MediaTrack.class));
307 310
verify(racingEventService, never()).mediaTrackDeleted(any(MediaTrack.class));
java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaLibaryTest.java
... ...
@@ -1,29 +1,31 @@
1 1
package com.sap.sailing.server.impl;
2 2
3
-import static org.hamcrest.core.Is.is;
4
-import static org.junit.Assert.assertEquals;
5
-import static org.junit.Assert.assertFalse;
6
-import static org.junit.Assert.assertThat;
7
-import static org.junit.Assert.assertTrue;
8
-
9 3
import java.util.Collection;
10
-import java.util.Date;
11 4
import java.util.Iterator;
12 5
import java.util.Set;
13 6
14 7
import org.junit.Before;
15 8
import org.junit.Test;
16 9
10
+import com.sap.sailing.domain.common.Duration;
11
+import com.sap.sailing.domain.common.TimePoint;
12
+import com.sap.sailing.domain.common.TimeRange;
13
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
14
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
15
+import com.sap.sailing.domain.common.impl.TimeRangeImpl;
17 16
import com.sap.sailing.domain.common.media.MediaTrack;
18 17
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
19
-import com.sap.sailing.server.impl.MediaLibrary.Interval;
18
+
19
+import static org.junit.Assert.*;
20
+
21
+import static org.hamcrest.Matchers.*;
20 22
21 23
22 24
public class MediaLibaryTest {
23 25
24
- private static final int FIFTEEN_MINUTES_IN_MILLIS = 15 * 60 * 1000;
25
- private static final int THIRTY_MINUTES_IN_MILLIS = 30 * 60 * 1000;
26
- private static final int ONE_HOUR_IN_MILLIS = 60 * 60 * 1000;
26
+ private static final Duration FIFTEEN_MINUTES_IN_MILLIS = MillisecondsDurationImpl.ONE_MINUTE.times(15);
27
+ private static final Duration THIRTY_MINUTES_IN_MILLIS = MillisecondsDurationImpl.ONE_MINUTE.times(30);
28
+ private static final Duration ONE_HOUR_IN_MILLIS = MillisecondsDurationImpl.ONE_HOUR;
27 29
28 30
private MediaLibrary mediaLibary;
29 31
... ...
@@ -36,10 +38,10 @@ public class MediaLibaryTest {
36 38
private MediaTrack createMediaTrack(String dbId) {
37 39
String title = "title";
38 40
String url = "url";
39
- Date startTime = new Date();
40
- int durationInMillis = 1;
41
+ TimePoint startTime = MillisecondsTimePoint.now();
42
+ Duration duration = MillisecondsDurationImpl.ONE_HOUR;
41 43
MimeType mimeType = MimeType.mp4;
42
- MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, durationInMillis, mimeType);
44
+ MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, duration, mimeType);
43 45
return mediaTrack;
44 46
}
45 47
... ...
@@ -61,7 +63,7 @@ public class MediaLibaryTest {
61 63
assertThat(item.title, is(mediaTrack.title));
62 64
assertThat(item.url, is(mediaTrack.url));
63 65
assertThat(item.startTime, is(mediaTrack.startTime));
64
- assertThat(item.durationInMillis, is(mediaTrack.durationInMillis));
66
+ assertThat(item.duration, is(mediaTrack.duration));
65 67
assertThat(item.mimeType, is(mediaTrack.mimeType));
66 68
67 69
}
... ...
@@ -78,80 +80,80 @@ public class MediaLibaryTest {
78 80
79 81
@Test
80 82
public void testQueryMediaTracksBetween_MatchingStartTimes_TrackLongerThanEndTime() {
81
- Date startTime = new Date();
82
- int duration = ONE_HOUR_IN_MILLIS;
83
- Date rangeStart = startTime;
84
- Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
83
+ TimePoint startTime = MillisecondsTimePoint.now();
84
+ Duration duration = ONE_HOUR_IN_MILLIS;
85
+ TimePoint rangeStart = startTime;
86
+ TimePoint rangeEnd = rangeStart.plus(THIRTY_MINUTES_IN_MILLIS);
85 87
86 88
assertOverlap(startTime, duration, rangeStart, rangeEnd);
87 89
}
88 90
89 91
@Test
90 92
public void testQueryMediaTracksBetween_MatchingStartTimes_TrackShorterThanEndTime() {
91
- Date startTime = new Date();
92
- int duration = THIRTY_MINUTES_IN_MILLIS;
93
- Date rangeStart = startTime;
94
- Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
93
+ TimePoint startTime = MillisecondsTimePoint.now();
94
+ Duration duration = THIRTY_MINUTES_IN_MILLIS;
95
+ TimePoint rangeStart = startTime;
96
+ TimePoint rangeEnd = rangeStart.plus(ONE_HOUR_IN_MILLIS);
95 97
96 98
assertOverlap(startTime, duration, rangeStart, rangeEnd);
97 99
}
98 100
99 101
@Test
100 102
public void testQueryMediaTracksBetween_StartsBeforeEndsAfterRange() {
101
- Date startTime = new Date();
102
- int duration = ONE_HOUR_IN_MILLIS;
103
- Date rangeStart = new Date(startTime.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
104
- Date rangeEnd = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
103
+ TimePoint startTime = MillisecondsTimePoint.now();
104
+ Duration duration = ONE_HOUR_IN_MILLIS;
105
+ TimePoint rangeStart = startTime.plus(FIFTEEN_MINUTES_IN_MILLIS);
106
+ TimePoint rangeEnd = rangeStart.plus(FIFTEEN_MINUTES_IN_MILLIS);
105 107
106 108
assertOverlap(startTime, duration, rangeStart, rangeEnd);
107 109
}
108 110
109 111
@Test
110 112
public void testQueryMediaTracksBetween_StartsAfterEndsBeforeRange() {
111
- Date rangeStart = new Date();
112
- Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
113
- Date startTime = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
114
- int duration = FIFTEEN_MINUTES_IN_MILLIS;
113
+ TimePoint rangeStart = MillisecondsTimePoint.now();
114
+ TimePoint rangeEnd = rangeStart.plus(ONE_HOUR_IN_MILLIS);
115
+ TimePoint startTime = rangeStart.plus(FIFTEEN_MINUTES_IN_MILLIS);
116
+ Duration duration = FIFTEEN_MINUTES_IN_MILLIS;
115 117
116 118
assertOverlap(startTime, duration, rangeStart, rangeEnd);
117 119
}
118 120
119 121
@Test
120 122
public void testQueryMediaTracksBetween_StartsAfterEndsAfterRange() {
121
- Date rangeStart = new Date();
122
- Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
123
- Date startTime = new Date(rangeStart.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
124
- int duration = THIRTY_MINUTES_IN_MILLIS;
123
+ TimePoint rangeStart = MillisecondsTimePoint.now();
124
+ TimePoint rangeEnd = rangeStart.plus(THIRTY_MINUTES_IN_MILLIS);
125
+ TimePoint startTime = rangeStart.plus(FIFTEEN_MINUTES_IN_MILLIS);
126
+ Duration duration = THIRTY_MINUTES_IN_MILLIS;
125 127
126 128
assertOverlap(startTime, duration, rangeStart, rangeEnd);
127 129
}
128 130
129 131
@Test
130 132
public void testQueryMediaTracksBetween_StartsBeforeEndsBeforeRange() {
131
- Date startTime = new Date();
132
- int duration = THIRTY_MINUTES_IN_MILLIS;
133
- Date rangeStart = new Date(startTime.getTime() + FIFTEEN_MINUTES_IN_MILLIS);
134
- Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
133
+ TimePoint startTime = MillisecondsTimePoint.now();
134
+ Duration duration = THIRTY_MINUTES_IN_MILLIS;
135
+ TimePoint rangeStart = startTime.plus(FIFTEEN_MINUTES_IN_MILLIS);
136
+ TimePoint rangeEnd = rangeStart.plus(THIRTY_MINUTES_IN_MILLIS);
135 137
136 138
assertOverlap(startTime, duration, rangeStart, rangeEnd);
137 139
}
138 140
139 141
@Test
140 142
public void testQueryMediaTracksBetween_TrackEndsBeforeRange() {
141
- Date startTime = new Date();
142
- int duration = THIRTY_MINUTES_IN_MILLIS;
143
- Date rangeStart = new Date(startTime.getTime() + duration + 1);
144
- Date rangeEnd = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
143
+ TimePoint startTime = MillisecondsTimePoint.now();
144
+ Duration duration = THIRTY_MINUTES_IN_MILLIS;
145
+ TimePoint rangeStart = startTime.plus(duration).plus(1);
146
+ TimePoint rangeEnd = rangeStart.plus(ONE_HOUR_IN_MILLIS);
145 147
146 148
assertNoOverlap(startTime, duration, rangeStart, rangeEnd);
147 149
}
148 150
149 151
@Test
150 152
public void testQueryMediaTracksBetween_TrackStartsAfterRange() {
151
- Date rangeStart = new Date();
152
- Date rangeEnd = new Date(rangeStart.getTime() + THIRTY_MINUTES_IN_MILLIS);
153
- Date startTime = new Date(rangeStart.getTime() + ONE_HOUR_IN_MILLIS);
154
- int duration = THIRTY_MINUTES_IN_MILLIS;
153
+ TimePoint rangeStart = MillisecondsTimePoint.now();
154
+ TimePoint rangeEnd = rangeStart.plus(THIRTY_MINUTES_IN_MILLIS);
155
+ TimePoint startTime = rangeStart.plus(ONE_HOUR_IN_MILLIS);
156
+ Duration duration = THIRTY_MINUTES_IN_MILLIS;
155 157
156 158
assertNoOverlap(startTime, duration, rangeStart, rangeEnd);
157 159
}
... ...
@@ -160,11 +162,11 @@ public class MediaLibaryTest {
160 162
public void testCacheChangeStartTime() {
161 163
MediaTrack originalMediaTrack = new MediaTrack();
162 164
originalMediaTrack.dbId = "a";
163
- originalMediaTrack.startTime = new Date();
164
- originalMediaTrack.durationInMillis = 100;
165
+ originalMediaTrack.startTime = MillisecondsTimePoint.now();
166
+ originalMediaTrack.duration = new MillisecondsDurationImpl(1);
165 167
166
- Date queryStartTime = new Date(originalMediaTrack.startTime.getTime() + 1);
167
- Date queryEndTime = new Date(originalMediaTrack.deriveEndTime().getTime() - 1);
168
+ TimePoint queryStartTime = originalMediaTrack.startTime.minus(1);
169
+ TimePoint queryEndTime = originalMediaTrack.deriveEndTime().plus(1);
168 170
169 171
mediaLibary.addMediaTrack(originalMediaTrack);
170 172
... ...
@@ -173,8 +175,8 @@ public class MediaLibaryTest {
173 175
174 176
MediaTrack changedMediaTrack = new MediaTrack();
175 177
changedMediaTrack.dbId = "a";
176
- changedMediaTrack.startTime = new Date(originalMediaTrack.startTime.getTime() + 101);
177
- changedMediaTrack.durationInMillis = 100;
178
+ changedMediaTrack.startTime = originalMediaTrack.startTime.plus(101);
179
+ changedMediaTrack.duration = new MillisecondsDurationImpl(100);
178 180
mediaLibary.startTimeChanged(changedMediaTrack);
179 181
180 182
Set<MediaTrack> secondQueryResult = mediaLibary.findMediaTracksInTimeRange(queryStartTime, queryEndTime);
... ...
@@ -190,11 +192,11 @@ public class MediaLibaryTest {
190 192
public void testCacheRemoveMediaTrack() {
191 193
MediaTrack mediaTrack = new MediaTrack();
192 194
mediaTrack.dbId = "a";
193
- mediaTrack.startTime = new Date();
194
- mediaTrack.durationInMillis = 100;
195
+ mediaTrack.startTime = MillisecondsTimePoint.now();
196
+ mediaTrack.duration = MillisecondsDurationImpl.ONE_HOUR;
195 197
196
- Date originalStartTime = mediaTrack.startTime;
197
- Date originalEndTime = mediaTrack.deriveEndTime();
198
+ TimePoint originalStartTime = mediaTrack.startTime;
199
+ TimePoint originalEndTime = mediaTrack.deriveEndTime();
198 200
199 201
mediaLibary.addMediaTrack(mediaTrack);
200 202
... ...
@@ -212,12 +214,12 @@ public class MediaLibaryTest {
212 214
public void testCacheAddSecondMediaTrackWithSameInterval() {
213 215
MediaTrack firstMediaTrack = new MediaTrack();
214 216
firstMediaTrack.dbId = "a";
215
- firstMediaTrack.startTime = new Date();
216
- firstMediaTrack.durationInMillis = 100;
217
+ firstMediaTrack.startTime = MillisecondsTimePoint.now();
218
+ firstMediaTrack.duration = MillisecondsDurationImpl.ONE_HOUR;
217 219
mediaLibary.addMediaTrack(firstMediaTrack);
218 220
219
- Date queryStartTime = new Date(firstMediaTrack.startTime.getTime() + 1);
220
- Date queryEndTime = new Date(firstMediaTrack.deriveEndTime().getTime() - 1);
221
+ TimePoint queryStartTime = firstMediaTrack.startTime.plus(1);
222
+ TimePoint queryEndTime = firstMediaTrack.deriveEndTime().minus(1);
221 223
222 224
Set<MediaTrack> firstQueryResult = mediaLibary.findMediaTracksInTimeRange(queryStartTime, queryEndTime);
223 225
assertThat(firstQueryResult.size(), is(1));
... ...
@@ -225,26 +227,26 @@ public class MediaLibaryTest {
225 227
MediaTrack secondMediaTrack = new MediaTrack();
226 228
secondMediaTrack.dbId = "b";
227 229
secondMediaTrack.startTime = firstMediaTrack.startTime;
228
- secondMediaTrack.durationInMillis = firstMediaTrack.durationInMillis;
230
+ secondMediaTrack.duration = firstMediaTrack.duration;
229 231
mediaLibary.addMediaTrack(secondMediaTrack);
230 232
231 233
Set<MediaTrack> secondQueryResult = mediaLibary.findMediaTracksInTimeRange(queryStartTime, queryEndTime);
232 234
assertThat(secondQueryResult.size(), is(2));
233 235
234
- Date uncachedStartTime = new Date(firstMediaTrack.startTime.getTime() + 2);
235
- Date uncachedEndTime = new Date(firstMediaTrack.deriveEndTime().getTime() - 2);
236
+ TimePoint uncachedStartTime = firstMediaTrack.startTime.plus(2);
237
+ TimePoint uncachedEndTime = firstMediaTrack.deriveEndTime().minus(2);
236 238
237 239
Set<MediaTrack> thirdQueryResult = mediaLibary.findMediaTracksInTimeRange(uncachedStartTime, uncachedEndTime);
238 240
assertThat(thirdQueryResult.size(), is(2));
239 241
}
240 242
241
- private void assertOverlap(Date startTime, int durationInMillis, Date rangeStart, Date rangeEnd) {
243
+ private void assertOverlap(TimePoint startTime, Duration duration, TimePoint rangeStart, TimePoint rangeEnd) {
242 244
//insert test object
243 245
String dbId = "1234";
244 246
String title = "title";
245 247
String url = "url";
246 248
MimeType mimeType = MimeType.mp4;
247
- MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, durationInMillis, mimeType);
249
+ MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, duration, mimeType);
248 250
mediaLibary.addMediaTrack(mediaTrack);
249 251
250 252
Collection<MediaTrack> mediaTracks = mediaLibary.findMediaTracksInTimeRange(rangeStart, rangeEnd);
... ...
@@ -252,13 +254,13 @@ public class MediaLibaryTest {
252 254
assertThat(mediaTracks.iterator().next().dbId, is(dbId));
253 255
}
254 256
255
- private void assertNoOverlap(Date startTime, int durationInMillis, Date rangeStart, Date rangeEnd) {
257
+ private void assertNoOverlap(TimePoint startTime, Duration duration, TimePoint rangeStart, TimePoint rangeEnd) {
256 258
//insert test object
257 259
String dbId = "1234";
258 260
String title = "title";
259 261
String url = "url";
260 262
MimeType mimeType = MimeType.mp4;
261
- MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, durationInMillis, mimeType);
263
+ MediaTrack mediaTrack = new MediaTrack(dbId, title, url, startTime, duration, mimeType);
262 264
mediaLibary.addMediaTrack(mediaTrack);
263 265
264 266
Collection<MediaTrack> mediaTracks = mediaLibary.findMediaTracksInTimeRange(rangeStart, rangeEnd);
... ...
@@ -267,40 +269,40 @@ public class MediaLibaryTest {
267 269
268 270
@Test
269 271
public void testIntervalEqualsIdentical() throws Exception {
270
- Date date1 = new Date();
271
- Date date2 = new Date(date1.getTime() + 1);
272
- Interval interval = new Interval(date1, date2);
272
+ TimePoint date1 = MillisecondsTimePoint.now();
273
+ TimePoint date2 = date1.plus(1);
274
+ TimeRange interval = new TimeRangeImpl(date1, date2);
273 275
assertTrue(interval.equals(interval));
274 276
}
275 277
276 278
@Test
277 279
public void testIntervalEqualsSame() throws Exception {
278
- Date date1_1 = new Date();
279
- Date date1_2 = new Date(date1_1.getTime() + 1);
280
- Date date2_1 = new Date(date1_1.getTime());
281
- Date date2_2 = new Date(date1_2.getTime());
282
- Interval interval1 = new Interval(date1_1, date1_2);
283
- Interval interval2 = new Interval(date2_1, date2_2);
280
+ TimePoint date1_1 = MillisecondsTimePoint.now();
281
+ TimePoint date1_2 = date1_1.plus(1);
282
+ TimePoint date2_1 = date1_1;
283
+ TimePoint date2_2 = date1_2;
284
+ TimeRange interval1 = new TimeRangeImpl(date1_1, date1_2);
285
+ TimeRange interval2 = new TimeRangeImpl(date2_1, date2_2);
284 286
assertTrue(interval1.equals(interval2));
285 287
assertEquals(interval1.hashCode(), interval2.hashCode());
286 288
}
287 289
288 290
@Test
289 291
public void testIntervalEqualsNull() throws Exception {
290
- Date date1 = new Date();
291
- Date date2 = new Date(date1.getTime() + 1);
292
- Interval interval = new Interval(date1, date2);
292
+ TimePoint date1 = MillisecondsTimePoint.now();
293
+ TimePoint date2 = date1.plus(1);
294
+ TimeRange interval = new TimeRangeImpl(date1, date2);
293 295
assertFalse(interval.equals(null));
294 296
}
295 297
296 298
@Test
297 299
public void testIntervalNotEquals() throws Exception {
298
- Date date1_1 = new Date();
299
- Date date1_2 = new Date(date1_1.getTime() + 1);
300
- Date date2_1 = new Date(date1_1.getTime() + 2);
301
- Date date2_2 = new Date(date1_1.getTime() + 3);
302
- Interval interval1 = new Interval(date1_1, date1_2);
303
- Interval interval2 = new Interval(date2_1, date2_2);
300
+ TimePoint date1_1 = MillisecondsTimePoint.now();
301
+ TimePoint date1_2 = date1_1.plus(1);
302
+ TimePoint date2_1 = date1_1.plus(2);
303
+ TimePoint date2_2 = date1_1.plus(3);
304
+ TimeRange interval1 = new TimeRangeImpl(date1_1, date1_2);
305
+ TimeRange interval2 = new TimeRangeImpl(date2_1, date2_2);
304 306
assertFalse(interval1.equals(interval2));
305 307
}
306 308
java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java
... ...
@@ -3,6 +3,7 @@ package com.sap.sailing.server.test;
3 3
import static org.junit.Assert.assertEquals;
4 4
import static org.junit.Assert.assertNotNull;
5 5
import static org.junit.Assert.assertSame;
6
+
6 7
import static org.mockito.Mockito.doReturn;
7 8
import static org.mockito.Mockito.spy;
8 9
... ...
@@ -71,6 +72,7 @@ import com.sap.sailing.domain.common.impl.DataImportProgressImpl;
71 72
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
72 73
import com.sap.sailing.domain.common.impl.DegreePosition;
73 74
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
75
+import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
74 76
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
75 77
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
76 78
import com.sap.sailing.domain.common.media.MediaTrack;
... ...
@@ -162,7 +164,7 @@ public class MasterDataImportTest {
162 164
ClassNotFoundException {
163 165
// Setup source service
164 166
RacingEventService sourceService = new RacingEventServiceImpl();
165
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
167
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
166 168
UUID courseAreaUUID = UUID.randomUUID();
167 169
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
168 170
event.getVenue().addCourseArea(courseArea);
... ...
@@ -378,7 +380,7 @@ public class MasterDataImportTest {
378 380
InterruptedException, ClassNotFoundException {
379 381
// Setup source service
380 382
RacingEventService sourceService = new RacingEventServiceImpl();
381
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
383
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
382 384
UUID courseAreaUUID = UUID.randomUUID();
383 385
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
384 386
event.getVenue().addCourseArea(courseArea);
... ...
@@ -534,7 +536,7 @@ public class MasterDataImportTest {
534 536
ClassNotFoundException {
535 537
// Setup source service
536 538
RacingEventService sourceService = new RacingEventServiceImpl();
537
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
539
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
538 540
UUID courseAreaUUID = UUID.randomUUID();
539 541
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
540 542
event.getVenue().addCourseArea(courseArea);
... ...
@@ -676,7 +678,7 @@ public class MasterDataImportTest {
676 678
PersistenceFactory.INSTANCE.getDomainObjectFactory(MongoDBService.INSTANCE, sourceDomainFactory),
677 679
PersistenceFactory.INSTANCE.getMongoObjectFactory(MongoDBService.INSTANCE),
678 680
MediaDBFactory.INSTANCE.getDefaultMediaDB(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE);
679
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
681
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
680 682
UUID courseAreaUUID = UUID.randomUUID();
681 683
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
682 684
event.getVenue().addCourseArea(courseArea);
... ...
@@ -831,7 +833,7 @@ public class MasterDataImportTest {
831 833
InterruptedException, ClassNotFoundException {
832 834
// Setup source service
833 835
RacingEventService sourceService = new RacingEventServiceImpl();
834
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
836
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
835 837
UUID courseAreaUUID = UUID.randomUUID();
836 838
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
837 839
event.getVenue().addCourseArea(courseArea);
... ...
@@ -945,8 +947,8 @@ public class MasterDataImportTest {
945 947
domainFactory = destService.getBaseDomainFactory();
946 948
// Create existing data on target
947 949
venueNameNotToOverride = "doNotOverride";
948
- Event eventNotToOverride = destService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, venueNameNotToOverride, false,
949
- eventUUID);
950
+ Event eventNotToOverride = destService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, venueNameNotToOverride,
951
+ false, eventUUID);
950 952
courseAreaNotToOverride = new CourseAreaImpl("testAreaNotToOverride", courseAreaUUID);
951 953
eventNotToOverride.getVenue().addCourseArea(courseAreaNotToOverride);
952 954
... ...
@@ -1029,7 +1031,7 @@ public class MasterDataImportTest {
1029 1031
InterruptedException, ClassNotFoundException {
1030 1032
// Setup source service
1031 1033
RacingEventService sourceService = new RacingEventServiceImpl();
1032
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
1034
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
1033 1035
UUID courseAreaUUID = UUID.randomUUID();
1034 1036
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
1035 1037
event.getVenue().addCourseArea(courseArea);
... ...
@@ -1139,7 +1141,7 @@ public class MasterDataImportTest {
1139 1141
domainFactory = destService.getBaseDomainFactory();
1140 1142
// Create existing data on target
1141 1143
String venueNameToOverride = "Override";
1142
- Event eventToOverride = destService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, venueNameToOverride, false, eventUUID);
1144
+ Event eventToOverride = destService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, venueNameToOverride, false, eventUUID);
1143 1145
CourseArea courseAreaToOverride = new CourseAreaImpl("testAreaToOverride", courseAreaUUID);
1144 1146
eventToOverride.getVenue().addCourseArea(courseAreaToOverride);
1145 1147
... ...
@@ -1256,12 +1258,15 @@ public class MasterDataImportTest {
1256 1258
1257 1259
Iterable<URL> imageURLs = new HashSet<>();
1258 1260
Iterable<URL> videoURLs = new HashSet<>();
1259
- Event event = sourceService.createEventWithoutReplication("Test Event", new MillisecondsTimePoint(0),
1260
- new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, videoURLs);
1261
+ Iterable<URL> sponsorImageURLs = new HashSet<>();
1262
+ Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */ null,
1263
+ new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs,
1264
+ videoURLs, sponsorImageURLs, /* logoImageURL */ null, /* officialWebsiteURL */ null);
1261 1265
CourseArea defaultCourseArea = sourceService.addCourseArea(event.getId(), "ECHO", UUID.randomUUID());
1262 1266
1263
- Regatta regatta = sourceService.createRegatta(RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, UUID.randomUUID(),
1264
- new ArrayList<Series>(), true, new LowPoint(), defaultCourseArea.getId(), /* useStartTimeInference */ true);
1267
+ Regatta regatta = sourceService.createRegatta(
1268
+ RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME,
1269
+ UUID.randomUUID(), new ArrayList<Series>(), true, new LowPoint(), defaultCourseArea.getId(), /* useStartTimeInference */ true);
1265 1270
// Let's use the setters directly because we are not testing replication
1266 1271
RegattaConfigurationImpl configuration = new RegattaConfigurationImpl();
1267 1272
configuration.setDefaultCourseDesignerMode(CourseDesignerMode.BY_MAP);
... ...
@@ -1320,8 +1325,10 @@ public class MasterDataImportTest {
1320 1325
1321 1326
Iterable<URL> imageURLs = new HashSet<>();
1322 1327
Iterable<URL> videoURLs = new HashSet<>();
1323
- Event event = sourceService.createEventWithoutReplication("Test Event", new MillisecondsTimePoint(0),
1324
- new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs, videoURLs);
1328
+ Iterable<URL> sponsorImageURLs = new HashSet<>();
1329
+ Event event = sourceService.createEventWithoutReplication("Test Event", /* eventDescription */ null,
1330
+ new MillisecondsTimePoint(0), new MillisecondsTimePoint(10), "testvenue", false, UUID.randomUUID(), imageURLs,
1331
+ videoURLs, sponsorImageURLs, /* logoImageURL */ null, /* officialWebsiteURL */ null);
1325 1332
CourseArea defaultCourseArea = sourceService.addCourseArea(event.getId(), "ECHO", UUID.randomUUID());
1326 1333
1327 1334
List<String> raceColumnNames = new ArrayList<String>();
... ...
@@ -1450,7 +1457,7 @@ public class MasterDataImportTest {
1450 1457
IOException, InterruptedException, ClassNotFoundException {
1451 1458
// Setup source service
1452 1459
RacingEventService sourceService = new RacingEventServiceImpl();
1453
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
1460
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
1454 1461
UUID courseAreaUUID = UUID.randomUUID();
1455 1462
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
1456 1463
event.getVenue().addCourseArea(courseArea);
... ...
@@ -1599,7 +1606,7 @@ public class MasterDataImportTest {
1599 1606
ClassNotFoundException {
1600 1607
// Setup source service
1601 1608
RacingEventService sourceService = new RacingEventServiceImpl();
1602
- MediaTrack trackOnSource = new MediaTrack("testTitle", "http://test/test.mp4", new Date(0), 2000,
1609
+ MediaTrack trackOnSource = new MediaTrack("testTitle", "http://test/test.mp4", new MillisecondsTimePoint(0), MillisecondsDurationImpl.ONE_HOUR,
1603 1610
MediaTrack.MimeType.mp4);
1604 1611
sourceService.mediaTrackAdded(trackOnSource);
1605 1612
... ...
@@ -1656,7 +1663,7 @@ public class MasterDataImportTest {
1656 1663
InterruptedException, ClassNotFoundException {
1657 1664
// Setup source service
1658 1665
RacingEventService sourceService = new RacingEventServiceImpl();
1659
- Event event = sourceService.addEvent(TEST_EVENT_NAME, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
1666
+ Event event = sourceService.addEvent(TEST_EVENT_NAME, /* eventDescription */ null, eventStartDate, eventEndDate, "testVenue", false, eventUUID);
1660 1667
UUID courseAreaUUID = UUID.randomUUID();
1661 1668
CourseArea courseArea = new CourseAreaImpl("testArea", courseAreaUUID);
1662 1669
event.getVenue().addCourseArea(courseArea);
java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java
... ...
@@ -117,8 +117,10 @@ public class SearchServiceTest {
117 117
final TimePoint pfingstbuschStartDate = new MillisecondsTimePoint(cal.getTime());
118 118
cal.set(2014, 5, 8, 16, 00);
119 119
final TimePoint pfingstbuschEndDate = new MillisecondsTimePoint(cal.getTime());
120
- pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", pfingstbuschStartDate, pfingstbuschEndDate, "Kiel", /* isPublic */
121
- true, UUID.randomUUID(), Collections.<URL>emptySet(), Collections.<URL>emptySet()));
120
+ pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", /* eventDescription */ null, pfingstbuschStartDate, pfingstbuschEndDate, /* isPublic */
121
+ "Kiel", true, UUID.randomUUID(), Collections.<URL>emptySet(),
122
+ Collections.<URL>emptySet(),
123
+ /* sponsorImageURLs */ Collections.<URL>emptySet(), /* logoImageURLAsString */ null, /* officialWebsiteURLAsString */ null));
122 124
kiel = pfingstbusch.getVenue();
123 125
final CourseAreaImpl kielAlpha = new CourseAreaImpl("Alpha", UUID.randomUUID());
124 126
kiel.addCourseArea(kielAlpha);
... ...
@@ -148,8 +150,10 @@ public class SearchServiceTest {
148 150
final TimePoint aalStartDate = new MillisecondsTimePoint(cal.getTime());
149 151
cal.set(2014, 5, 8, 18, 00);
150 152
final TimePoint aalEndDate = new MillisecondsTimePoint(cal.getTime());
151
- aalEvent = server.apply(new CreateEvent("Aalregatta", aalStartDate, aalEndDate, "Flensburg", /* isPublic */
152
- true, UUID.randomUUID(), Collections.<URL>emptySet(), Collections.<URL>emptySet()));
153
+ aalEvent = server.apply(new CreateEvent("Aalregatta", /* eventDescription */ null, aalStartDate, aalEndDate, /* isPublic */
154
+ "Flensburg", true, UUID.randomUUID(), Collections.<URL>emptySet(),
155
+ Collections.<URL>emptySet(), /* sponsorImageURLs */ Collections.<URL>emptySet(),
156
+ /* logoimageURL */ null, /* officialWebsiteURLAsString */ null));
153 157
flensburg = aalEvent.getVenue();
154 158
final CourseAreaImpl flensburgStandard = new CourseAreaImpl("Standard", UUID.randomUUID());
155 159
flensburg.addCourseArea(flensburgStandard);
java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java
... ...
@@ -340,6 +340,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
340 340
*
341 341
* @param eventName
342 342
* The name of the new event
343
+ * @param eventDescription TODO
343 344
* @param startDate
344 345
* The start date of the event
345 346
* @param endDate
... ...
@@ -352,7 +353,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
352 353
* The name of the venue of the new event
353 354
* @return The new event
354 355
*/
355
- Event addEvent(String eventName, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id);
356
+ Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id);
356 357
357 358
/**
358 359
* Updates a sailing event with the name <code>eventName</code>, the venue<code>venue</code> and the regattas with
... ...
@@ -459,8 +460,8 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
459 460
*/
460 461
ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs();
461 462
462
- Event createEventWithoutReplication(String eventName, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic,
463
- UUID id, Iterable<URL> imageURLs, Iterable<URL> videoURLs);
463
+ Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
464
+ boolean isPublic, UUID id, Iterable<URL> imageURLs, Iterable<URL> videoURLs, Iterable<URL> sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL);
464 465
465 466
void setRegattaForRace(Regatta regatta, String raceIdAsString);
466 467
java/com.sap.sailing.server/src/com/sap/sailing/server/impl/MediaLibrary.java
... ...
@@ -4,10 +4,8 @@ import java.io.IOException;
4 4
import java.io.ObjectInputStream;
5 5
import java.io.ObjectOutputStream;
6 6
import java.util.ArrayList;
7
-import java.util.Arrays;
8 7
import java.util.Collection;
9 8
import java.util.Collections;
10
-import java.util.Date;
11 9
import java.util.HashMap;
12 10
import java.util.HashSet;
13 11
import java.util.Map;
... ...
@@ -16,43 +14,15 @@ import java.util.Set;
16 14
import java.util.concurrent.ConcurrentHashMap;
17 15
import java.util.concurrent.ConcurrentMap;
18 16
17
+import com.sap.sailing.domain.common.TimePoint;
18
+import com.sap.sailing.domain.common.TimeRange;
19
+import com.sap.sailing.domain.common.impl.TimeRangeImpl;
19 20
import com.sap.sailing.domain.common.media.MediaTrack;
20
-import com.sap.sailing.domain.common.media.MediaUtil;
21 21
import com.sap.sailing.util.impl.LockUtil;
22 22
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
23 23
24 24
class MediaLibrary {
25 25
26
- static class Interval {
27
-
28
- public final Date begin;
29
- public final Date end;
30
-
31
- public Interval(Date begin, Date end) {
32
- this.begin = begin;
33
- this.end = end;
34
- }
35
-
36
- @Override
37
- public boolean equals(Object obj) {
38
- if (this == obj) {
39
- return true;
40
- } else if (obj instanceof Interval) {
41
- Interval interval = (Interval) obj;
42
- return MediaUtil.equalsDatesAllowingNull(this.begin, interval.begin)
43
- && MediaUtil.equalsDatesAllowingNull(this.end, interval.end);
44
- } else {
45
- return false;
46
- }
47
- }
48
-
49
- @Override
50
- public int hashCode() {
51
- return Arrays.hashCode(new Date[] { begin, end });
52
- }
53
-
54
- }
55
-
56 26
/**
57 27
* The set of MediaTracks kept by this library. Due to complex write operations which require explicit write-locking
58 28
* anyway there's no need to realize this collection with built-in concurrency support.
... ...
@@ -65,7 +35,7 @@ class MediaLibrary {
65 35
* result in case a MediaTrack is being removed from the library or changes values such that it needs to be removed
66 36
* from the cache.
67 37
*/
68
- private final ConcurrentMap<Interval, Set<MediaTrack>> cacheByInterval = new ConcurrentHashMap<Interval, Set<MediaTrack>>();
38
+ private final ConcurrentMap<TimeRange, Set<MediaTrack>> cacheByInterval = new ConcurrentHashMap<TimeRange, Set<MediaTrack>>();
69 39
70 40
private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock(MediaLibrary.class.getName(), /* fair */ false);
71 41
... ...
@@ -104,11 +74,11 @@ class MediaLibrary {
104 74
* @param endTime
105 75
* @return
106 76
*/
107
- Set<MediaTrack> findMediaTracksInTimeRange(Date startTime, Date endTime) {
77
+ Set<MediaTrack> findMediaTracksInTimeRange(TimePoint startTime, TimePoint endTime) {
108 78
109 79
if (startTime != null) {
110 80
111
- Interval interval = new Interval(startTime, endTime);
81
+ TimeRange interval = new TimeRangeImpl(startTime, endTime);
112 82
LockUtil.lockForRead(lock);
113 83
try {
114 84
Set<MediaTrack> cachedMediaTracks = cacheByInterval.get(interval);
... ...
@@ -138,6 +108,16 @@ class MediaLibrary {
138 108
return Collections.emptySet();
139 109
}
140 110
111
+ public Collection<MediaTrack> findLiveMediaTracksForRace(String regattaName, String raceName) {
112
+ Set<MediaTrack> result = new HashSet<MediaTrack>();
113
+ for (MediaTrack mediaTrack : mediaTracksByDbId.values()) {
114
+ if (mediaTrack.duration == null) {
115
+ result.add(mediaTrack);
116
+ }
117
+ }
118
+ return result;
119
+ }
120
+
141 121
/**
142 122
* Adding media tracks is expected to happen so rarely that we don't optimize for it and simply re-use the add-many
143 123
* method.
... ...
@@ -224,7 +204,7 @@ class MediaLibrary {
224 204
try {
225 205
MediaTrack mediaTrack = mediaTracksByDbId.get(changedMediaTrack);
226 206
if (mediaTrack != null) {
227
- mediaTrack.durationInMillis = changedMediaTrack.durationInMillis;
207
+ mediaTrack.duration = changedMediaTrack.duration;
228 208
updateCache_Change(mediaTrack);
229 209
}
230 210
} finally {
... ...
@@ -236,9 +216,9 @@ class MediaLibrary {
236 216
* To be called only under write lock!
237 217
*/
238 218
private void updateCache_Add(MediaTrack mediaTrack) {
239
- for (Entry<Interval, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
240
- Interval interval = cacheEntry.getKey();
241
- if (mediaTrack.overlapsWith(interval.begin, interval.end)) {
219
+ for (Entry<TimeRange, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
220
+ TimeRange interval = cacheEntry.getKey();
221
+ if (mediaTrack.overlapsWith(interval.from(), interval.to())) {
242 222
cacheEntry.getValue().add(mediaTrack);
243 223
}
244 224
}
... ...
@@ -248,10 +228,10 @@ class MediaLibrary {
248 228
* To be called only under write lock!
249 229
*/
250 230
private void updateCache_Change(MediaTrack mediaTrack) {
251
- for (Entry<Interval, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
231
+ for (Entry<TimeRange, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
252 232
cacheEntry.getValue().remove(mediaTrack);
253
- Interval interval = cacheEntry.getKey();
254
- if (mediaTrack.overlapsWith(interval.begin, interval.end)) {
233
+ TimeRange interval = cacheEntry.getKey();
234
+ if (mediaTrack.overlapsWith(interval.from(), interval.to())) {
255 235
cacheEntry.getValue().add(mediaTrack);
256 236
}
257 237
}
... ...
@@ -261,7 +241,7 @@ class MediaLibrary {
261 241
* To be called only under write lock!
262 242
*/
263 243
private void updateCache_Remove(MediaTrack mediaTrack) {
264
- for (Entry<Interval, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
244
+ for (Entry<TimeRange, Set<MediaTrack>> cacheEntry : cacheByInterval.entrySet()) {
265 245
cacheEntry.getValue().remove(mediaTrack);
266 246
}
267 247
}
java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java
... ...
@@ -14,7 +14,6 @@ import java.net.URLEncoder;
14 14
import java.util.ArrayList;
15 15
import java.util.Collection;
16 16
import java.util.Collections;
17
-import java.util.Date;
18 17
import java.util.HashMap;
19 18
import java.util.HashSet;
20 19
import java.util.Iterator;
... ...
@@ -82,6 +81,7 @@ import com.sap.sailing.domain.common.dto.FleetDTO;
82 81
import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
83 82
import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO;
84 83
import com.sap.sailing.domain.common.impl.DataImportProgressImpl;
84
+import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
85 85
import com.sap.sailing.domain.common.media.MediaTrack;
86 86
import com.sap.sailing.domain.common.media.MediaTrack.MimeType;
87 87
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
... ...
@@ -560,7 +560,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
560 560
for (DBMediaTrack dbMediaTrack : allDBMediaTracks) {
561 561
MimeType mimeType = dbMediaTrack.mimeType != null ? MimeType.byName(dbMediaTrack.mimeType) : null;
562 562
MediaTrack mediaTrack = new MediaTrack(dbMediaTrack.dbId, dbMediaTrack.title, dbMediaTrack.url,
563
- dbMediaTrack.startTime, dbMediaTrack.durationInMillis, mimeType);
563
+ dbMediaTrack.startTime, dbMediaTrack.duration, mimeType);
564 564
mediaTrackAdded(mediaTrack);
565 565
}
566 566
}
... ...
@@ -2294,11 +2294,14 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
2294 2294
2295 2295
// Used for TESTING only
2296 2296
@Override
2297
- public Event addEvent(String eventName, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic, UUID id) {
2298
- Event result = createEventWithoutReplication(eventName, startDate, endDate, venue, isPublic, id,
2299
- /* imageURLs */ Collections.<URL>emptyList(), /* videoURLs */ Collections.<URL>emptyList());
2300
- replicate(new CreateEvent(eventName, startDate, endDate, venue, isPublic, id, /* imageURLs */
2301
- Collections.<URL> emptyList(), /* videoURLs */Collections.<URL> emptyList()));
2297
+ public Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic, UUID id) {
2298
+ Event result = createEventWithoutReplication(eventName, eventDescription, startDate, endDate, venue, isPublic,
2299
+ id, /* imageURLs */ Collections.<URL>emptyList(),
2300
+ /* videoURLs */ Collections.<URL>emptyList(), /* sponsorImageURLs */ Collections.<URL>emptyList(), /* logoImageURL */ null, /* officialWebsiteURL */ null);
2301
+ replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, /* imageURLs */
2302
+ id, Collections.<URL> emptyList(),
2303
+ /* videoURLs */Collections.<URL> emptyList(), /* sponsorImageURLs */ Collections.<URL> emptyList(),
2304
+ /* logoimageURL */ null, /* officialWebsiteURLAsString */ null));
2302 2305
return result;
2303 2306
}
2304 2307
... ...
@@ -2308,12 +2311,17 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
2308 2311
}
2309 2312
2310 2313
@Override
2311
- public Event createEventWithoutReplication(String eventName, TimePoint startDate, TimePoint endDate, String venue,
2312
- boolean isPublic, UUID id, Iterable<URL> imageURLs, Iterable<URL> videoURLs) {
2314
+ public Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
2315
+ String venue, boolean isPublic, UUID id, Iterable<URL> imageURLs,
2316
+ Iterable<URL> videoURLs, Iterable<URL> sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL) {
2313 2317
Event result = new EventImpl(eventName, startDate, endDate, venue, isPublic, id);
2314 2318
addEvent(result);
2319
+ result.setDescription(eventDescription);
2315 2320
result.setImageURLs(imageURLs);
2316 2321
result.setVideoURLs(videoURLs);
2322
+ result.setSponsorImageURLs(sponsorImageURLs);
2323
+ result.setLogoImageURL(logoImageURL);
2324
+ result.setOfficialWebsiteURL(officialWebsiteURL);
2317 2325
return result;
2318 2326
}
2319 2327
... ...
@@ -2427,7 +2435,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
2427 2435
String mimeType = mediaTrack.mimeType != null ? mediaTrack.mimeType.name() : null;
2428 2436
if (mediaTrack.dbId == null) {
2429 2437
mediaTrack.dbId = mediaDB.insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime,
2430
- mediaTrack.durationInMillis, mimeType);
2438
+ mediaTrack.duration, mimeType);
2431 2439
}
2432 2440
mediaLibrary.addMediaTrack(mediaTrack);
2433 2441
replicate(new AddMediaTrackOperation(mediaTrack));
... ...
@@ -2461,7 +2469,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
2461 2469
2462 2470
@Override
2463 2471
public void mediaTrackDurationChanged(MediaTrack mediaTrack) {
2464
- mediaDB.updateDuration(mediaTrack.dbId, mediaTrack.durationInMillis);
2472
+ mediaDB.updateDuration(mediaTrack.dbId, mediaTrack.duration);
2465 2473
mediaLibrary.durationChanged(mediaTrack);
2466 2474
replicate(new UpdateMediaTrackDurationOperation(mediaTrack));
2467 2475
}
... ...
@@ -2478,7 +2486,7 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
2478 2486
for (MediaTrack trackToImport : mediaTracksToImport) {
2479 2487
MediaTrack existingTrack = mediaLibrary.lookupMediaTrack(trackToImport);
2480 2488
if (existingTrack == null) {
2481
- mediaDB.insertMediaTrackWithId(trackToImport.dbId, trackToImport.title, trackToImport.url, trackToImport.startTime, trackToImport.durationInMillis, trackToImport.mimeType.name());
2489
+ mediaDB.insertMediaTrackWithId(trackToImport.dbId, trackToImport.title, trackToImport.url, trackToImport.startTime, trackToImport.duration, trackToImport.mimeType.name());
2482 2490
mediaTrackAdded(trackToImport);
2483 2491
} else if (override) {
2484 2492
... ...
@@ -2498,8 +2506,8 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
2498 2506
existingTrack.startTime = trackToImport.startTime;
2499 2507
mediaTrackStartTimeChanged(existingTrack);
2500 2508
}
2501
- if (existingTrack.durationInMillis != trackToImport.durationInMillis) {
2502
- existingTrack.durationInMillis = trackToImport.durationInMillis;
2509
+ if (existingTrack.duration != trackToImport.duration) {
2510
+ existingTrack.duration = trackToImport.duration;
2503 2511
mediaTrackDurationChanged(existingTrack);
2504 2512
}
2505 2513
}
... ...
@@ -2510,9 +2518,13 @@ public class RacingEventServiceImpl implements RacingEventServiceWithTestSupport
2510 2518
public Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
2511 2519
TrackedRace trackedRace = getExistingTrackedRace(regattaAndRaceIdentifier);
2512 2520
if (trackedRace != null) {
2513
- Date raceStart = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asDate();
2514
- Date raceEnd = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate();
2515
- return mediaLibrary.findMediaTracksInTimeRange(raceStart, raceEnd);
2521
+ if (trackedRace.isLive(MillisecondsTimePoint.now())) {
2522
+ return mediaLibrary.findLiveMediaTracksForRace(trackedRace.getRaceIdentifier().getRegattaName(), trackedRace.getRaceIdentifier().getRaceName());
2523
+ } else {
2524
+ TimePoint raceStart = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace();
2525
+ TimePoint raceEnd = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace();
2526
+ return mediaLibrary.findMediaTracksInTimeRange(raceStart, raceEnd);
2527
+ }
2516 2528
} else {
2517 2529
return Collections.emptyList();
2518 2530
}
java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java
... ...
@@ -22,19 +22,27 @@ public class CreateEvent extends AbstractEventOperation<Event> {
22 22
private final TimePoint endDate;
23 23
private final boolean isPublic;
24 24
private final String eventName;
25
+ private final String eventDescription;
25 26
private final Iterable<URL> videoURLs;
26 27
private final Iterable<URL> imageURLs;
28
+ private final Iterable<URL> sponsorImageURLs;
29
+ private final URL logoImageURL;
30
+ private final URL officialWebsiteURL;
27 31
28
- public CreateEvent(String eventName, TimePoint startDate, TimePoint endDate, String venue, boolean isPublic,
29
- UUID id, Iterable<URL> imageURLs, Iterable<URL> videoURLs) {
32
+ public CreateEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
33
+ boolean isPublic, UUID id, Iterable<URL> imageURLs, Iterable<URL> videoURLs, Iterable<URL> sponsorImageURLs, URL logoImageURL, URL officialWebsiteURL) {
30 34
super(id);
31 35
this.eventName = eventName;
36
+ this.eventDescription = eventDescription;
32 37
this.startDate = startDate;
33 38
this.endDate = endDate;
34 39
this.venue = venue;
35 40
this.isPublic = isPublic;
36 41
this.imageURLs = imageURLs;
37 42
this.videoURLs = videoURLs;
43
+ this.sponsorImageURLs = sponsorImageURLs;
44
+ this.logoImageURL = logoImageURL;
45
+ this.officialWebsiteURL = officialWebsiteURL;
38 46
}
39 47
40 48
@Override
... ...
@@ -55,7 +63,8 @@ public class CreateEvent extends AbstractEventOperation<Event> {
55 63
56 64
@Override
57 65
public Event internalApplyTo(RacingEventService toState) {
58
- return toState.createEventWithoutReplication(getEventName(), startDate, endDate, venue, isPublic, getId(), imageURLs, videoURLs);
66
+ return toState.createEventWithoutReplication(getEventName(), eventDescription, startDate, endDate, venue, isPublic,
67
+ getId(), imageURLs, videoURLs, sponsorImageURLs, logoImageURL, officialWebsiteURL);
59 68
}
60 69
61 70
}
java/com.sap.sailing.simulator.test/src/com/sap/sailing/simulator/test/PolarDiagramCSVTest.java
... ...
@@ -9,6 +9,7 @@ import junit.framework.Assert;
9 9
10 10
import org.junit.Test;
11 11
12
+import com.sap.sailing.domain.common.AbstractBearing;
12 13
import com.sap.sailing.domain.common.Bearing;
13 14
import com.sap.sailing.domain.common.Speed;
14 15
import com.sap.sailing.domain.common.SpeedWithBearing;
... ...
@@ -168,7 +169,7 @@ public class PolarDiagramCSVTest {
168 169
169 170
private static void testSpeedsAtBearingsOfPolarDiagrams(PolarDiagram expectedPD, PolarDiagram actualPD,
170 171
String typeName) {
171
- DegreeBearingImpl testBearing = null;
172
+ AbstractBearing testBearing = null;
172 173
Speed actualSpeed = null;
173 174
Speed expectedSpeed = null;
174 175
SpeedWithBearing wind = null;
java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PathGeneratorTreeGrowWind.java
... ...
@@ -31,7 +31,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
31 31
private static Logger logger = Logger.getLogger("com.sap.sailing");
32 32
private boolean debugMsgOn = false;
33 33
34
- double oobFact = 0.75; // out-of-bounds factor
34
+ double oobFact = 2.0; // out-of-bounds factor
35 35
int maxTurns = 0;
36 36
boolean upwindLeg = false;
37 37
String initPathStr = "0";
... ...
@@ -560,7 +560,6 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
560 560
System.out.println("Horizontal Bin Size: "+hrzBinSize);
561 561
}
562 562
563
- //double oobFact = 0.75; // out-of-bounds factor
564 563
boolean reachedEnd = false;
565 564
int addSteps = 0;
566 565
int finalSteps = 0; // maximum number of additional steps after first target-path found
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DialogUtils.java
... ...
@@ -32,6 +32,14 @@ public abstract class DialogUtils {
32 32
}
33 33
}
34 34
});
35
+ widget.addKeyUpHandler(new KeyUpHandler() {
36
+ @Override
37
+ public void onKeyUp(KeyUpEvent event) {
38
+ if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
39
+ button.click();
40
+ }
41
+ }
42
+ });
35 43
}
36 44
}
37 45
mobile/com.sap.sailing.racecommittee.app/res/layout/confirm_dialog.xml
... ...
@@ -0,0 +1,21 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+
3
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
4
+ android:orientation="vertical"
5
+ android:layout_width="wrap_content"
6
+ android:layout_height="wrap_content">
7
+
8
+ <TextView
9
+ android:layout_width="match_parent"
10
+ android:layout_height="wrap_content"
11
+ android:layout_marginTop="8dp"
12
+ android:layout_marginLeft="4dp"
13
+ android:layout_marginRight="4dp"
14
+ android:layout_marginBottom="8dp"
15
+ android:textSize="12pt"
16
+ android:textStyle="bold"
17
+ android:gravity="center"
18
+ android:text="@string/confirm_general_recall"
19
+ android:lines="1"/>
20
+
21
+</LinearLayout>
... ...
\ No newline at end of file
mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml
... ...
@@ -28,6 +28,9 @@
28 28
<string name="abort_running_race">Abbruch</string>
29 29
<string name="choose_xray_flag_up">Einzelrückruf</string>
30 30
<string name="choose_first_substitute_flag">Gesamtrückruf</string>
31
+ <string name="confirm_general_recall">General Recall ausführen?</string>
32
+ <string name="confirm">Bestätigen</string>
33
+ <string name="abort">Abbruch</string>
31 34
<string name="choose_blue_flag">Zieleinlauf beginnen</string>
32 35
<string name="flag_ap">AP Flagge</string>
33 36
<string name="flag_november">November</string>
mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml
... ...
@@ -30,6 +30,9 @@
30 30
<string name="choose_xray_flag_up">Display Individual Recall</string>
31 31
<string name="choose_xray_flag_down">Remove Individual Recall</string>
32 32
<string name="choose_first_substitute_flag">General Recall</string>
33
+ <string name="confirm_general_recall">Execute General Recall?</string>
34
+ <string name="confirm">Confirm</string>
35
+ <string name="abort">Cancel</string>
33 36
<string name="choose_blue_flag">Begin finish phase</string>
34 37
<string name="flag_ap">AP Flag</string>
35 38
<string name="flag_november">November</string>
mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/running/BaseRunningRaceFragment.java
... ...
@@ -19,6 +19,7 @@ import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.AbortTypeSelection
19 19
import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.RaceDialogFragment;
20 20
import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.RaceFinishingTimeDialog;
21 21
import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.BaseRaceInfoRaceFragment;
22
+import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.running.ConfirmDialog.ConfirmRecallListener;
22 23
import com.sap.sailing.racecommittee.app.ui.utils.FlagPoleStateRenderer;
23 24
import com.sap.sailing.racecommittee.app.utils.TimeUtils;
24 25
... ...
@@ -83,10 +84,20 @@ public abstract class BaseRunningRaceFragment<ProcedureType extends RacingProced
83 84
generalRecallButton.setOnClickListener(new OnClickListener() {
84 85
@Override
85 86
public void onClick(View v) {
86
- TimePoint now = MillisecondsTimePoint.now();
87
- getRaceState().setGeneralRecall(now);
88
- // TODO see bug 1649: Explicit passing of pass identifier in RaceState interface
89
- getRaceState().setAdvancePass(now);
87
+ ConfirmDialog confirmDialog = new ConfirmDialog();
88
+ confirmDialog.setTargetFragment(BaseRunningRaceFragment.this, 1);
89
+ confirmDialog.setCallback(new ConfirmRecallListener() {
90
+ @Override
91
+ public void returnAddedElementToPicker(boolean recall) {
92
+ TimePoint now = MillisecondsTimePoint.now();
93
+ getRaceState().setGeneralRecall(now);
94
+ // TODO see bug 1649: Explicit passing of pass identifier in RaceState interface
95
+ getRaceState().setAdvancePass(now);
96
+ }
97
+ });
98
+ final String tag = "confirm_general_recall_fragment";
99
+ confirmDialog.show(getFragmentManager(), tag);
100
+
90 101
}
91 102
});
92 103
mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/running/ConfirmDialog.java
... ...
@@ -0,0 +1,48 @@
1
+package com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.running;
2
+
3
+import android.app.AlertDialog;
4
+import android.app.Dialog;
5
+import android.app.DialogFragment;
6
+import android.content.DialogInterface;
7
+import android.os.Bundle;
8
+import android.view.LayoutInflater;
9
+import android.view.View;
10
+
11
+import com.sap.sailing.racecommittee.app.R;
12
+
13
+public class ConfirmDialog extends DialogFragment {
14
+ private ConfirmRecallListener confirmRecallListener;
15
+
16
+ public interface ConfirmRecallListener {
17
+ public void returnAddedElementToPicker(boolean recall);
18
+ }
19
+
20
+ public void setCallback(ConfirmRecallListener callback) {
21
+ this.confirmRecallListener = callback;
22
+ }
23
+
24
+ @Override
25
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
26
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
27
+ LayoutInflater inflater = getActivity().getLayoutInflater();
28
+ final View view = inflater.inflate(R.layout.confirm_dialog, null);
29
+ builder.setView(view).setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
30
+ @Override
31
+ public void onClick(DialogInterface dialog, int id) {
32
+ returnData();
33
+ }
34
+ }).setNegativeButton(R.string.abort, new DialogInterface.OnClickListener() {
35
+ public void onClick(DialogInterface dialog, int id) {
36
+ ConfirmDialog.this.getDialog().cancel();
37
+ }
38
+ });
39
+
40
+ return builder.create();
41
+ }
42
+
43
+ private void returnData() {
44
+ if (confirmRecallListener != null) {
45
+ confirmRecallListener.returnAddedElementToPicker(true);
46
+ }
47
+ }
48
+}
... ...
\ No newline at end of file
wiki/amazon-ec2.md
... ...
@@ -266,14 +266,15 @@ A closer look reveals that an ELB instance consists itself of many other invisib
266 266
Here are the steps to create a load balanced setup:
267 267
268 268
- Create a master instance holding all data (see http://wiki.sapsailing.com/wiki/amazon-ec2#Setting-up-Master-and-Replica)
269
+- When using the Race Committee App (RCApp), make sure the app is configured to send its data to the master instance and not the ELB (otherwise, write requests may end up at replicas which they wouldn't know how to handle). You may want to configure a separate URL for the master server for this purpose, so you don't have to re-configure the RCApp devices when switching to a different master server.
269 270
- Create `n` instances that are configured to connect to the master server
270 271
- Create a load balancer that redirects everything from HTTP port 8888 to HTTP port 8888 and leave the other switches and checkboxes on their default value
271 272
- As "Ping Port" enter HTTP port 8888 and use /index.html as the "Ping Path." Leave the other values on their defaults again.
272 273
- Put the ELB into the "Sailing Analytics App" security group as it will appear in the landscape as a regular sailing analytics application server.
273 274
- Associate all your instances
274
-- Connect your domain with the IP of the load balancer. It could be a good idea to use an Elastic IP that always stays the same for the domain and associate it with your load balancer. That way you can also easily switch between a load balancer and a single instance setup.
275
+- Connect your domain with the IP of the load balancer. It could be a good idea to use an Elastic IP that always stays the same for the domain and associate it with your load balancer. That way you can also easily switch between a load balancer and a single instance setup. Again, remember not to let the RCApp devices point to the ELB domain as their updates could hit a replica which wouldn't know how to handle!
275 276
276
-Amazon ELB is designed to handle unlimited concurrent requests per second with “gradually increasing” load pattern (although it's initial capacity is described to reach 20k requests/secs). It is not designed to handle heavy sudden spike of load or flash traffic because of it's internal structure where it needs to fire up more instances when load increases. ELB's can be pre-warmed though by writing to the AWS Support Team.
277
+Amazon ELB is designed to handle unlimited concurrent requests per second with “gradually increasing” load pattern (although it's initial capacity is described to reach 20k requests/secs). It is not designed to handle heavy sudden spike of load or flash traffic because of its internal structure where it needs to fire up more instances when load increases. ELB's can be pre-warmed though by writing to the AWS Support Team.
277 278
278 279
### Access MongoDB database
279 280
wiki/images/winscp/winscp-create-folder.png
... ...
Binary files /dev/null and b/wiki/images/winscp/winscp-create-folder.png differ
wiki/images/winscp/winscp-install-1.png
... ...
Binary files /dev/null and b/wiki/images/winscp/winscp-install-1.png differ
wiki/images/winscp/winscp-keys.png
... ...
Binary files /dev/null and b/wiki/images/winscp/winscp-keys.png differ
wiki/images/winscp/winscp-login.png
... ...
Binary files /dev/null and b/wiki/images/winscp/winscp-login.png differ
wiki/images/winscp/winscp-var-www-static.png
... ...
Binary files /dev/null and b/wiki/images/winscp/winscp-var-www-static.png differ
wiki/racelog-tracking/app-spec/.gitignore
... ...
@@ -1 +0,0 @@
1
-app-spec.pdf
wiki/racelog-tracking/app-spec/app-spec.md
... ...
@@ -50,12 +50,14 @@ First the use cases for the tracking app are presented, which should give a good
50 50
The proposed smartphone app should enable casual users to apply the Sailing Analytics to their own tracking data. The following paragraphs describe the most important use cases in this context from the viewpoint of the users, leaving the technical details for a later section. The use cases are ordered by their importance - the ones that should be supported first are presented first.
51 51
52 52
### Individual Training
53
-This can be considered as being the simplest use-case. After downloading the app, the sailor creates a user account. Alternatively, he may log in to an existing account. As is common for apps this login should persist across app and phone restarts, until the users explicitly logs out. The user data could also serve as the basis for the competitor data (for which additional information about the country, sail-number and boatclass is required), but this discussed later on in the technical addendum.
53
+<div class="image"><img src="login.png" width="400px"/></div>
54
+
55
+This can be considered as being the simplest use-case. After downloading the app, the sailor creates a user account. Alternatively, he may log in to an existing account. As is common for apps this login should persist across app and phone restarts, until the users explicitly logs out. The user data could also serve as the basis for the competitor data (for which additional information about the country, sail-number and boatclass is required).
54 56
55 57
!!! server
56 58
Clarify how user and competitor management play together. Are they entirely separate, and a user can create different competitors for his account, and switch between them, or is a user the same thing as a competitor? What happens, e.g., if a user switches to a different boat - changing the boat class and sail-number in his user preferences would cause wrong information for old races. Instead of having to login with a different user, instead the user might select from the list of competitors he has created from that account, or choose to add a new one.
57 59
58
-<div class="image"><img src="training.svg"/></div>
60
+<div class="image"><img src="training.png" width="700px"/></div>
59 61
60 62
On the following start screen there is a dominant button for starting the tracking - this functionality has to be readily available at all times, as missing out on tracking data is the potentially worst thing that can happen - everything else can be done later on. During tracking, the following information should be shown:
61 63
... ...
@@ -186,6 +188,25 @@ The Leaderboard is a pretty complex table. It includes several levels of hierarc
186 188
An important part of the app surely also lies in managing the existing tracks and races generated from these tracks. On the one hand, the user may simply wish to see what tracks are on his phone (and what their synchronization status to the server is), and browser through his races. The list of races then serves as an entrypoint for analysis or sharing.
187 189
188 190
191
+### Start Line Analysis
192
+This app is targeted at sailors as users - enabling them to track and analyze their own training sessions and races. To improve, a sailor needs to know which part of his performance can be optimized. Apart from the RaceBoard, a detailed start line analysis can be helpful. Interesting figures include:
193
+
194
+* distance to start line at time of start
195
+* start on left or right side of line
196
+* on which tack was the boat at time of start
197
+* speed on crossing the start line
198
+* speed at time of start
199
+* rank for the first few 10s-intervals (rank at 10s/20s/... into the race)
200
+
201
+In addition to the figures for the own boat, these figures could be compared to those of other competitors when analyzing a race. By doing so, a sailor can learn what others are doing better or worse than him.
202
+
203
+
204
+### Social Media Integration
205
+Enabling social interaction within the app can increase the visibility of both the app and the Sailing Analytics. Part of this is allowing users to invite others to participate in a race. Social interaction within the app is one part of this: users can invite other users to participate in their race, and communicate directly e.g. by setting the start time.
206
+
207
+Connecting to existing social networks and communication channels is another simple yet effective measure to this end. A race or regatta (represented by a URL) can be easily shared through these.
208
+The URL should be handled by the app if available. Otherwise a mobile representation of the requested site should be rendered, ideally with an additional recommendation on installing the app.
209
+
189 210
190 211
## Technical Addendum
191 212
This addendum addresses more technical issues that seem obvious from the point in time of writing this documentation. More technical issues will surely arise during implementation, but the following sections try to cover the larger areas that can already be foreseen.
... ...
@@ -217,6 +238,37 @@ The question arises, which other steps should also be made accessible through a
217 238
Basically, this means duplicating functionality (maybe even for the third time: complex regatta management in the AdminConsole, simple wizard on the smartphone, intermediate one in the Smartphone-Tracking web interface) to allow user to perform difficult or more complex tasks at their computer. This does not have to be implemented immediately, at least we can wait to see for which steps users would like to see this possibility most.
218 239
219 240
241
+### Necessary APIs
242
+The various components of the app that have been described have to interact with the existing Sailing Analytics backend. Some of these interactions are entirely new, others already exist - but only for the web interface (through the _GWT-RPC_ channel). Other interactions again are already available in the form of _RESTful JSON APIs_ which are used by the RaceCommittee App. The following list gives an overview of the data and interactions which span both mobile device and backend.
243
+
244
+* Account Management
245
+ * Creating an account
246
+ * Logging in
247
+ * Searching user list
248
+* Competitor Management
249
+ * Creating a new competitor
250
+ * Editing an existing competitor
251
+* Submitting GPS Fixes
252
+* Submitting further sensor data and multimedia
253
+* Track Management
254
+ * Retrieving list
255
+ * Defining visibility
256
+* Race Management
257
+ * Retrieving list
258
+ * Creating a race
259
+ * Course definition
260
+ * Setting start time
261
+ * Inviting users
262
+ * Defining visibility
263
+* Regatta Management
264
+ * Retrieving list
265
+ * Creating a regatta
266
+ * Adding a race
267
+ * Defining visibility
268
+* RaceBoard
269
+ * Variety of measurements and calculated figures
270
+
271
+
220 272
### Backend Implementation Details
221 273
222 274
#### Receiving and storing Tracking Data
wiki/racelog-tracking/app-spec/app-spec.pdf
... ...
Binary files /dev/null and b/wiki/racelog-tracking/app-spec/app-spec.pdf differ
wiki/racelog-tracking/app-spec/login.png
... ...
Binary files /dev/null and b/wiki/racelog-tracking/app-spec/login.png differ
wiki/racelog-tracking/app-spec/training.dia
... ...
Binary files /dev/null and b/wiki/racelog-tracking/app-spec/training.dia differ
wiki/racelog-tracking/app-spec/training.graphml
... ...
@@ -1,892 +0,0 @@
1
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
-<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
3
- <!--Created by yEd 3.12.2-->
4
- <key for="graphml" id="d0" yfiles.type="resources"/>
5
- <key for="port" id="d1" yfiles.type="portgraphics"/>
6
- <key for="port" id="d2" yfiles.type="portgeometry"/>
7
- <key for="port" id="d3" yfiles.type="portuserdata"/>
8
- <key attr.name="url" attr.type="string" for="node" id="d4"/>
9
- <key attr.name="description" attr.type="string" for="node" id="d5"/>
10
- <key for="node" id="d6" yfiles.type="nodegraphics"/>
11
- <key attr.name="Description" attr.type="string" for="graph" id="d7"/>
12
- <key attr.name="url" attr.type="string" for="edge" id="d8"/>
13
- <key attr.name="description" attr.type="string" for="edge" id="d9"/>
14
- <key for="edge" id="d10" yfiles.type="edgegraphics"/>
15
- <graph edgedefault="directed" id="G">
16
- <data key="d7"/>
17
- <node id="n0">
18
- <data key="d5"/>
19
- <data key="d6">
20
- <y:ShapeNode>
21
- <y:Geometry height="10.0" width="87.11275724687482" x="401.2160000000001" y="708.7535999999998"/>
22
- <y:Fill hasColor="false" transparent="false"/>
23
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
24
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="15.640625" modelName="custom" textColor="#000000" visible="true" width="49.712890625" x="18.69993331093741" y="-2.8203125">Statistics<y:LabelModel>
25
- <y:SmartNodeLabelModel distance="4.0"/>
26
- </y:LabelModel>
27
- <y:ModelParameter>
28
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
29
- </y:ModelParameter>
30
- </y:NodeLabel>
31
- <y:Shape type="rectangle"/>
32
- </y:ShapeNode>
33
- </data>
34
- </node>
35
- <node id="n1" yfiles.foldertype="group">
36
- <data key="d4"/>
37
- <data key="d5"/>
38
- <data key="d6">
39
- <y:ProxyAutoBoundsNode>
40
- <y:Realizers active="0">
41
- <y:GroupNode>
42
- <y:Geometry height="210.0" width="142.0" x="371.0" y="425.0"/>
43
- <y:Fill hasColor="false" transparent="false"/>
44
- <y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
45
- <y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="142.0" x="0.0" y="0.0"/>
46
- <y:Shape type="roundrectangle"/>
47
- <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
48
- <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
49
- <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
50
- </y:GroupNode>
51
- <y:GroupNode>
52
- <y:Geometry height="50.0" width="50.0" x="176.0" y="318.4287999999998"/>
53
- <y:Fill color="#F5F5F5" transparent="false"/>
54
- <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
55
- <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="65.201171875" x="-7.6005859375" y="0.0">Folder 1</y:NodeLabel>
56
- <y:Shape type="roundrectangle"/>
57
- <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
58
- <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
59
- <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
60
- </y:GroupNode>
61
- </y:Realizers>
62
- </y:ProxyAutoBoundsNode>
63
- </data>
64
- <graph edgedefault="directed" id="n1:">
65
- <node id="n1::n0">
66
- <data key="d5"/>
67
- <data key="d6">
68
- <y:ShapeNode>
69
- <y:Geometry height="176.0" width="112.0" x="386.0" y="444.0"/>
70
- <y:Fill color="#000000" transparent="false"/>
71
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
72
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="54.0" y="86.0">
73
- <y:LabelModel>
74
- <y:SmartNodeLabelModel distance="4.0"/>
75
- </y:LabelModel>
76
- <y:ModelParameter>
77
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
78
- </y:ModelParameter>
79
- </y:NodeLabel>
80
- <y:Shape type="roundrectangle"/>
81
- </y:ShapeNode>
82
- </data>
83
- </node>
84
- <node id="n1::n1">
85
- <data key="d5"/>
86
- <data key="d6">
87
- <y:ShapeNode>
88
- <y:Geometry height="137.0" width="101.0" x="391.5" y="450.0"/>
89
- <y:Fill color="#FFFFFF" transparent="false"/>
90
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
91
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="48.5" y="66.5">
92
- <y:LabelModel>
93
- <y:SmartNodeLabelModel distance="4.0"/>
94
- </y:LabelModel>
95
- <y:ModelParameter>
96
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
97
- </y:ModelParameter>
98
- </y:NodeLabel>
99
- <y:Shape type="rectangle"/>
100
- </y:ShapeNode>
101
- </data>
102
- </node>
103
- <node id="n1::n2">
104
- <data key="d5"/>
105
- <data key="d6">
106
- <y:ShapeNode>
107
- <y:Geometry height="21.0" width="21.0" x="431.5" y="594.0"/>
108
- <y:Fill hasColor="false" transparent="false"/>
109
- <y:BorderStyle color="#FFFFFF" type="line" width="1.0"/>
110
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="8.5" y="8.5">
111
- <y:LabelModel>
112
- <y:SmartNodeLabelModel distance="4.0"/>
113
- </y:LabelModel>
114
- <y:ModelParameter>
115
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
116
- </y:ModelParameter>
117
- </y:NodeLabel>
118
- <y:Shape type="ellipse"/>
119
- </y:ShapeNode>
120
- </data>
121
- </node>
122
- </graph>
123
- </node>
124
- <node id="n2">
125
- <data key="d5"/>
126
- <data key="d6">
127
- <y:ShapeNode>
128
- <y:Geometry height="16.0" width="87.0" x="398.5" y="475.0"/>
129
- <y:Fill hasColor="false" transparent="false"/>
130
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
131
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="41.5" y="6.0">
132
- <y:LabelModel>
133
- <y:SmartNodeLabelModel distance="4.0"/>
134
- </y:LabelModel>
135
- <y:ModelParameter>
136
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
137
- </y:ModelParameter>
138
- </y:NodeLabel>
139
- <y:Shape type="rectangle"/>
140
- </y:ShapeNode>
141
- </data>
142
- </node>
143
- <node id="n3">
144
- <data key="d5"/>
145
- <data key="d6">
146
- <y:ShapeNode>
147
- <y:Geometry height="16.0" width="87.0" x="398.5" y="496.5"/>
148
- <y:Fill hasColor="false" transparent="false"/>
149
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
150
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="41.5" y="6.0">
151
- <y:LabelModel>
152
- <y:SmartNodeLabelModel distance="4.0"/>
153
- </y:LabelModel>
154
- <y:ModelParameter>
155
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
156
- </y:ModelParameter>
157
- </y:NodeLabel>
158
- <y:Shape type="rectangle"/>
159
- </y:ShapeNode>
160
- </data>
161
- </node>
162
- <node id="n4">
163
- <data key="d5"/>
164
- <data key="d6">
165
- <y:ShapeNode>
166
- <y:Geometry height="16.0" width="49.0" x="417.5" y="520.0"/>
167
- <y:Fill color="#C0C0C0" transparent="false"/>
168
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
169
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="36.583984375" x="6.2080078125" y="-0.984375">Login<y:LabelModel>
170
- <y:SmartNodeLabelModel distance="4.0"/>
171
- </y:LabelModel>
172
- <y:ModelParameter>
173
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
174
- </y:ModelParameter>
175
- </y:NodeLabel>
176
- <y:Shape type="rectangle"/>
177
- </y:ShapeNode>
178
- </data>
179
- </node>
180
- <node id="n5">
181
- <data key="d5"/>
182
- <data key="d6">
183
- <y:ShapeNode>
184
- <y:Geometry height="10.0" width="93.0" x="395.5" y="559.4464"/>
185
- <y:Fill hasColor="false" transparent="false"/>
186
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
187
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="27.28125" modelName="custom" textColor="#000000" visible="true" width="69.2783203125" x="11.86083984375" y="-8.640625">already have
188
-an account?<y:LabelModel>
189
- <y:SmartNodeLabelModel distance="4.0"/>
190
- </y:LabelModel>
191
- <y:ModelParameter>
192
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
193
- </y:ModelParameter>
194
- </y:NodeLabel>
195
- <y:Shape type="rectangle"/>
196
- </y:ShapeNode>
197
- </data>
198
- </node>
199
- <node id="n6" yfiles.foldertype="group">
200
- <data key="d4"/>
201
- <data key="d5"/>
202
- <data key="d6">
203
- <y:ProxyAutoBoundsNode>
204
- <y:Realizers active="0">
205
- <y:GroupNode>
206
- <y:Geometry height="209.99999999999994" width="283.82875724687494" x="522.5" y="425.00000000000006"/>
207
- <y:Fill hasColor="false" transparent="false"/>
208
- <y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
209
- <y:NodeLabel alignment="right" autoSizePolicy="node_width" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="283.82875724687494" x="0.0" y="0.0"/>
210
- <y:Shape type="roundrectangle"/>
211
- <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
212
- <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
213
- <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
214
- </y:GroupNode>
215
- <y:GroupNode>
216
- <y:Geometry height="50.0" width="50.0" x="557.2799999999999" y="326.16"/>
217
- <y:Fill color="#F5F5F5" transparent="false"/>
218
- <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
219
- <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="65.201171875" x="-7.6005859375" y="0.0">Folder 1</y:NodeLabel>
220
- <y:Shape type="roundrectangle"/>
221
- <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
222
- <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
223
- <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
224
- </y:GroupNode>
225
- </y:Realizers>
226
- </y:ProxyAutoBoundsNode>
227
- </data>
228
- <graph edgedefault="directed" id="n6:">
229
- <node id="n6::n0">
230
- <data key="d5"/>
231
- <data key="d6">
232
- <y:ShapeNode>
233
- <y:Geometry height="176.0" width="112.0" x="537.5" y="444.00000000000006"/>
234
- <y:Fill color="#000000" transparent="false"/>
235
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
236
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="54.0" y="85.99999999999994">
237
- <y:LabelModel>
238
- <y:SmartNodeLabelModel distance="4.0"/>
239
- </y:LabelModel>
240
- <y:ModelParameter>
241
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
242
- </y:ModelParameter>
243
- </y:NodeLabel>
244
- <y:Shape type="roundrectangle"/>
245
- </y:ShapeNode>
246
- </data>
247
- </node>
248
- <node id="n6::n1">
249
- <data key="d5"/>
250
- <data key="d6">
251
- <y:ShapeNode>
252
- <y:Geometry height="137.0" width="101.0" x="543.0" y="450.00000000000006"/>
253
- <y:Fill color="#FFFFFF" transparent="false"/>
254
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
255
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="48.5" y="66.49999999999994">
256
- <y:LabelModel>
257
- <y:SmartNodeLabelModel distance="4.0"/>
258
- </y:LabelModel>
259
- <y:ModelParameter>
260
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
261
- </y:ModelParameter>
262
- </y:NodeLabel>
263
- <y:Shape type="rectangle"/>
264
- </y:ShapeNode>
265
- </data>
266
- </node>
267
- <node id="n6::n2">
268
- <data key="d5"/>
269
- <data key="d6">
270
- <y:ShapeNode>
271
- <y:Geometry height="21.0" width="21.0" x="583.0" y="594.0"/>
272
- <y:Fill hasColor="false" transparent="false"/>
273
- <y:BorderStyle color="#FFFFFF" type="line" width="1.0"/>
274
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="8.5" y="8.5">
275
- <y:LabelModel>
276
- <y:SmartNodeLabelModel distance="4.0"/>
277
- </y:LabelModel>
278
- <y:ModelParameter>
279
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
280
- </y:ModelParameter>
281
- </y:NodeLabel>
282
- <y:Shape type="ellipse"/>
283
- </y:ShapeNode>
284
- </data>
285
- </node>
286
- <node id="n6::n3">
287
- <data key="d5"/>
288
- <data key="d6">
289
- <y:ShapeNode>
290
- <y:Geometry height="3.785599999999988" width="53.603999999999814" x="734.2160000000001" y="481.3104000000001"/>
291
- <y:Fill hasColor="false" transparent="false"/>
292
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
293
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="24.801999999999907" y="-0.10720000000003438"/>
294
- <y:Shape type="rectangle"/>
295
- </y:ShapeNode>
296
- </data>
297
- </node>
298
- <node id="n6::n4">
299
- <data key="d5"/>
300
- <data key="d6">
301
- <y:ShapeNode>
302
- <y:Geometry height="15.463360000000023" width="92.32997959270392" x="698.998777654171" y="568.7535999999999"/>
303
- <y:Fill color="#C0C0C0" transparent="false"/>
304
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
305
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="15.640625" modelName="custom" textColor="#000000" visible="true" width="73.013671875" x="9.65815385885196" y="-0.08863250000001699">Stop Tracking<y:LabelModel>
306
- <y:SmartNodeLabelModel distance="4.0"/>
307
- </y:LabelModel>
308
- <y:ModelParameter>
309
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
310
- </y:ModelParameter>
311
- </y:NodeLabel>
312
- <y:Shape type="rectangle"/>
313
- </y:ShapeNode>
314
- </data>
315
- </node>
316
- </graph>
317
- </node>
318
- <node id="n7">
319
- <data key="d5"/>
320
- <data key="d6">
321
- <y:ShapeNode>
322
- <y:Geometry height="3.785599999999988" width="53.603999999999814" x="582.7160000000001" y="481.31040000000013"/>
323
- <y:Fill hasColor="false" transparent="false"/>
324
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
325
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="24.801999999999907" y="-0.10719999999997754"/>
326
- <y:Shape type="rectangle"/>
327
- </y:ShapeNode>
328
- </data>
329
- </node>
330
- <node id="n8" yfiles.foldertype="group">
331
- <data key="d4"/>
332
- <data key="d5"/>
333
- <data key="d6">
334
- <y:ProxyAutoBoundsNode>
335
- <y:Realizers active="0">
336
- <y:GroupNode>
337
- <y:Geometry height="46.0" width="123.34248550625011" x="531.8287572468749" y="460.2032000000001"/>
338
- <y:Fill hasColor="false" transparent="false"/>
339
- <y:BorderStyle hasColor="false" type="dashed" width="1.0"/>
340
- <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" modelName="internal" modelPosition="t" textColor="#000000" visible="false" width="123.34248550625011" x="0.0" y="0.0">Group 3</y:NodeLabel>
341
- <y:Shape type="roundrectangle"/>
342
- <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
343
- <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
344
- <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
345
- </y:GroupNode>
346
- <y:GroupNode>
347
- <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
348
- <y:Fill color="#F5F5F5" transparent="false"/>
349
- <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
350
- <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="65.201171875" x="-7.6005859375" y="0.0">Folder 3</y:NodeLabel>
351
- <y:Shape type="roundrectangle"/>
352
- <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
353
- <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
354
- <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
355
- </y:GroupNode>
356
- </y:Realizers>
357
- </y:ProxyAutoBoundsNode>
358
- </data>
359
- <graph edgedefault="directed" id="n8:">
360
- <node id="n8::n0">
361
- <data key="d5"/>
362
- <data key="d6">
363
- <y:ShapeNode>
364
- <y:Geometry height="16.0" width="93.0" x="546.8287572468749" y="475.2032000000001"/>
365
- <y:Fill color="#C0C0C0" transparent="false"/>
366
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
367
- <y:NodeLabel alignment="right" autoSizePolicy="content" borderDistance="0.0" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="44.5" y="6.0">
368
- <y:LabelModel>
369
- <y:SmartNodeLabelModel distance="0.0"/>
370
- </y:LabelModel>
371
- <y:ModelParameter>
372
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
373
- </y:ModelParameter>
374
- </y:NodeLabel>
375
- <y:Shape type="rectangle"/>
376
- </y:ShapeNode>
377
- </data>
378
- </node>
379
- <node id="n8::n1">
380
- <data key="d5"/>
381
- <data key="d6">
382
- <y:ShapeNode>
383
- <y:Geometry height="16.0" width="68.72320320000017" x="568.455637246875" y="475.2032000000001"/>
384
- <y:Fill hasColor="false" transparent="false"/>
385
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
386
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="15.640625" modelName="custom" textColor="#000000" visible="true" width="74.7080078125" x="-2.9924023062499145" y="0.1796875">Start Tracking<y:LabelModel>
387
- <y:SmartNodeLabelModel distance="4.0"/>
388
- </y:LabelModel>
389
- <y:ModelParameter>
390
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
391
- </y:ModelParameter>
392
- </y:NodeLabel>
393
- <y:Shape type="rectangle"/>
394
- </y:ShapeNode>
395
- </data>
396
- </node>
397
- <node id="n8::n2">
398
- <data key="d5"/>
399
- <data key="d6">
400
- <y:ShapeNode>
401
- <y:Geometry height="14.926720000000046" width="14.926720000000046" x="547.498777654171" y="475.7398400000001"/>
402
- <y:Fill color="#FF0000" transparent="false"/>
403
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
404
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="5.46336000000008" y="5.463360000000023">
405
- <y:LabelModel>
406
- <y:SmartNodeLabelModel distance="4.0"/>
407
- </y:LabelModel>
408
- <y:ModelParameter>
409
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
410
- </y:ModelParameter>
411
- </y:NodeLabel>
412
- <y:Shape type="ellipse"/>
413
- </y:ShapeNode>
414
- </data>
415
- </node>
416
- </graph>
417
- </node>
418
- <node id="n9">
419
- <data key="d5"/>
420
- <data key="d6">
421
- <y:ShapeNode>
422
- <y:Geometry height="15.463360000000023" width="92.32997959270392" x="547.498777654171" y="515.096"/>
423
- <y:Fill color="#C0C0C0" transparent="false"/>
424
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
425
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="15.640625" modelName="custom" textColor="#000000" visible="true" width="54.5712890625" x="18.87934526510196" y="-0.08863250000001699">My Tracks<y:LabelModel>
426
- <y:SmartNodeLabelModel distance="4.0"/>
427
- </y:LabelModel>
428
- <y:ModelParameter>
429
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
430
- </y:ModelParameter>
431
- </y:NodeLabel>
432
- <y:Shape type="rectangle"/>
433
- </y:ShapeNode>
434
- </data>
435
- </node>
436
- <node id="n10">
437
- <data key="d5"/>
438
- <data key="d6">
439
- <y:ShapeNode>
440
- <y:Geometry height="116.01600000000008" width="101.0" x="694.5" y="450.0"/>
441
- <y:Fill color="#99CCFF" transparent="false"/>
442
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
443
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#3366FF" visible="true" width="4.0" x="48.5" y="56.00800000000004">
444
- <y:LabelModel>
445
- <y:SmartNodeLabelModel distance="4.0"/>
446
- </y:LabelModel>
447
- <y:ModelParameter>
448
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
449
- </y:ModelParameter>
450
- </y:NodeLabel>
451
- <y:Shape type="rectangle"/>
452
- </y:ShapeNode>
453
- </data>
454
- </node>
455
- <node id="n11">
456
- <data key="d5"/>
457
- <data key="d6">
458
- <y:ShapeNode>
459
- <y:Geometry height="30.0" width="30.0" x="711.6919630120962" y="527.2550478827525"/>
460
- <y:Fill hasColor="false" transparent="false"/>
461
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
462
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="13.0" y="13.0">
463
- <y:LabelModel>
464
- <y:SmartNodeLabelModel distance="4.0"/>
465
- </y:LabelModel>
466
- <y:ModelParameter>
467
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
468
- </y:ModelParameter>
469
- </y:NodeLabel>
470
- <y:Shape type="rectangle"/>
471
- </y:ShapeNode>
472
- </data>
473
- </node>
474
- <node id="n12">
475
- <data key="d5"/>
476
- <data key="d6">
477
- <y:ShapeNode>
478
- <y:Geometry height="37.96672000000001" width="92.32997959270392" x="395.998777654171" y="708.7536"/>
479
- <y:Fill hasColor="false" transparent="false"/>
480
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
481
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="38.921875" modelName="custom" textColor="#000000" visible="true" width="53.08203125" x="19.62397417135196" y="-0.47757750000005217">Statistics:
482
-...
483
-...<y:LabelModel>
484
- <y:SmartNodeLabelModel distance="4.0"/>
485
- </y:LabelModel>
486
- <y:ModelParameter>
487
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
488
- </y:ModelParameter>
489
- </y:NodeLabel>
490
- <y:Shape type="rectangle"/>
491
- </y:ShapeNode>
492
- </data>
493
- </node>
494
- <node id="n13">
495
- <data key="d5"/>
496
- <data key="d6">
497
- <y:ShapeNode>
498
- <y:Geometry height="176.0" width="112.0" x="386.0" y="644.0"/>
499
- <y:Fill color="#000000" transparent="false"/>
500
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
501
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="54.0" y="86.0">
502
- <y:LabelModel>
503
- <y:SmartNodeLabelModel distance="4.0"/>
504
- </y:LabelModel>
505
- <y:ModelParameter>
506
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
507
- </y:ModelParameter>
508
- </y:NodeLabel>
509
- <y:Shape type="roundrectangle"/>
510
- </y:ShapeNode>
511
- </data>
512
- </node>
513
- <node id="n14">
514
- <data key="d5"/>
515
- <data key="d6">
516
- <y:ShapeNode>
517
- <y:Geometry height="137.0" width="101.0" x="391.5" y="650.0"/>
518
- <y:Fill color="#FFFFFF" transparent="false"/>
519
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
520
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="48.5" y="66.5">
521
- <y:LabelModel>
522
- <y:SmartNodeLabelModel distance="4.0"/>
523
- </y:LabelModel>
524
- <y:ModelParameter>
525
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
526
- </y:ModelParameter>
527
- </y:NodeLabel>
528
- <y:Shape type="rectangle"/>
529
- </y:ShapeNode>
530
- </data>
531
- </node>
532
- <node id="n15">
533
- <data key="d5"/>
534
- <data key="d6">
535
- <y:ShapeNode>
536
- <y:Geometry height="176.0" width="112.0" x="689.0" y="444.0"/>
537
- <y:Fill color="#000000" transparent="false"/>
538
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
539
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="54.0" y="86.0">
540
- <y:LabelModel>
541
- <y:SmartNodeLabelModel distance="4.0"/>
542
- </y:LabelModel>
543
- <y:ModelParameter>
544
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
545
- </y:ModelParameter>
546
- </y:NodeLabel>
547
- <y:Shape type="roundrectangle"/>
548
- </y:ShapeNode>
549
- </data>
550
- </node>
551
- <node id="n16">
552
- <data key="d5"/>
553
- <data key="d6">
554
- <y:ShapeNode>
555
- <y:Geometry height="137.0" width="101.0" x="694.5" y="450.0"/>
556
- <y:Fill color="#FFFFFF" transparent="false"/>
557
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
558
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="48.5" y="66.5">
559
- <y:LabelModel>
560
- <y:SmartNodeLabelModel distance="4.0"/>
561
- </y:LabelModel>
562
- <y:ModelParameter>
563
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
564
- </y:ModelParameter>
565
- </y:NodeLabel>
566
- <y:Shape type="rectangle"/>
567
- </y:ShapeNode>
568
- </data>
569
- </node>
570
- <node id="n17">
571
- <data key="d5"/>
572
- <data key="d6">
573
- <y:ShapeNode>
574
- <y:Geometry height="21.0" width="21.0" x="734.5" y="593.9999999999999"/>
575
- <y:Fill hasColor="false" transparent="false"/>
576
- <y:BorderStyle color="#FFFFFF" type="line" width="1.0"/>
577
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="8.5" y="8.5">
578
- <y:LabelModel>
579
- <y:SmartNodeLabelModel distance="4.0"/>
580
- </y:LabelModel>
581
- <y:ModelParameter>
582
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
583
- </y:ModelParameter>
584
- </y:NodeLabel>
585
- <y:Shape type="ellipse"/>
586
- </y:ShapeNode>
587
- </data>
588
- </node>
589
- <node id="n18">
590
- <data key="d5"/>
591
- <data key="d6">
592
- <y:ShapeNode>
593
- <y:Geometry height="3.785599999999988" width="53.603999999999814" x="431.2160000000001" y="681.3104000000001"/>
594
- <y:Fill hasColor="false" transparent="false"/>
595
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
596
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="24.801999999999907" y="-0.10720000000003438"/>
597
- <y:Shape type="rectangle"/>
598
- </y:ShapeNode>
599
- </data>
600
- </node>
601
- <node id="n19">
602
- <data key="d5"/>
603
- <data key="d6">
604
- <y:ShapeNode>
605
- <y:Geometry height="21.0" width="21.0" x="431.5" y="793.9999999999999"/>
606
- <y:Fill hasColor="false" transparent="false"/>
607
- <y:BorderStyle color="#FFFFFF" type="line" width="1.0"/>
608
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="8.5" y="8.5">
609
- <y:LabelModel>
610
- <y:SmartNodeLabelModel distance="4.0"/>
611
- </y:LabelModel>
612
- <y:ModelParameter>
613
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
614
- </y:ModelParameter>
615
- </y:NodeLabel>
616
- <y:Shape type="ellipse"/>
617
- </y:ShapeNode>
618
- </data>
619
- </node>
620
- <node id="n20">
621
- <data key="d5"/>
622
- <data key="d6">
623
- <y:ShapeNode>
624
- <y:Geometry height="15.463360000000023" width="92.32997959270392" x="395.998777654171" y="768.7535999999999"/>
625
- <y:Fill color="#C0C0C0" transparent="false"/>
626
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
627
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="15.640625" modelName="custom" textColor="#000000" visible="true" width="43.404296875" x="24.46284135885196" y="-0.08863250000001699">Analyze<y:LabelModel>
628
- <y:SmartNodeLabelModel distance="4.0"/>
629
- </y:LabelModel>
630
- <y:ModelParameter>
631
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
632
- </y:ModelParameter>
633
- </y:NodeLabel>
634
- <y:Shape type="rectangle"/>
635
- </y:ShapeNode>
636
- </data>
637
- </node>
638
- <node id="n21">
639
- <data key="d5"/>
640
- <data key="d6">
641
- <y:ShapeNode>
642
- <y:Geometry height="46.0" width="92.32997959270392" x="395.998777654171" y="654.9152"/>
643
- <y:Fill color="#99CCFF" transparent="false"/>
644
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
645
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#3366FF" visible="true" width="4.0" x="44.16498979635196" y="21.0">
646
- <y:LabelModel>
647
- <y:SmartNodeLabelModel distance="4.0"/>
648
- </y:LabelModel>
649
- <y:ModelParameter>
650
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
651
- </y:ModelParameter>
652
- </y:NodeLabel>
653
- <y:Shape type="rectangle"/>
654
- </y:ShapeNode>
655
- </data>
656
- </node>
657
- <node id="n22">
658
- <data key="d5"/>
659
- <data key="d6">
660
- <y:ShapeNode>
661
- <y:Geometry height="1.0" width="1.0" x="408.69196301209615" y="727.2550478827525"/>
662
- <y:Fill hasColor="false" transparent="false"/>
663
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
664
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="-1.5" y="-1.5">
665
- <y:LabelModel>
666
- <y:SmartNodeLabelModel distance="4.0"/>
667
- </y:LabelModel>
668
- <y:ModelParameter>
669
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
670
- </y:ModelParameter>
671
- </y:NodeLabel>
672
- <y:Shape type="rectangle"/>
673
- </y:ShapeNode>
674
- </data>
675
- </node>
676
- <node id="n23">
677
- <data key="d5"/>
678
- <data key="d6">
679
- <y:ShapeNode>
680
- <y:Geometry height="15.463360000000023" width="92.32997959270392" x="698.998777654171" y="568.7535999999999"/>
681
- <y:Fill color="#C0C0C0" transparent="false"/>
682
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
683
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="10" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="15.640625" modelName="custom" textColor="#000000" visible="true" width="73.013671875" x="9.65815385885196" y="-0.08863250000001699">Stop Tracking<y:LabelModel>
684
- <y:SmartNodeLabelModel distance="4.0"/>
685
- </y:LabelModel>
686
- <y:ModelParameter>
687
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
688
- </y:ModelParameter>
689
- </y:NodeLabel>
690
- <y:Shape type="rectangle"/>
691
- </y:ShapeNode>
692
- </data>
693
- </node>
694
- <node id="n24">
695
- <data key="d5"/>
696
- <data key="d6">
697
- <y:ShapeNode>
698
- <y:Geometry height="176.0" width="112.0" x="538.1550698020919" y="643.9819121172475"/>
699
- <y:Fill color="#000000" transparent="false"/>
700
- <y:BorderStyle color="#000000" type="line" width="1.0"/>
701
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="54.0" y="86.0">
702
- <y:LabelModel>
703
- <y:SmartNodeLabelModel distance="4.0"/>
704
- </y:LabelModel>
705
- <y:ModelParameter>
706
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
707
- </y:ModelParameter>
708
- </y:NodeLabel>
709
- <y:Shape type="roundrectangle"/>
710
- </y:ShapeNode>
711
- </data>
712
- </node>
713
- <node id="n25">
714
- <data key="d5"/>
715
- <data key="d6">
716
- <y:ShapeNode>
717
- <y:Geometry height="1.0" width="1.0" x="560.847032814188" y="727.23696"/>
718
- <y:Fill hasColor="false" transparent="false"/>
719
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
720
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="-1.5" y="-1.5">
721
- <y:LabelModel>
722
- <y:SmartNodeLabelModel distance="4.0"/>
723
- </y:LabelModel>
724
- <y:ModelParameter>
725
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
726
- </y:ModelParameter>
727
- </y:NodeLabel>
728
- <y:Shape type="rectangle"/>
729
- </y:ShapeNode>
730
- </data>
731
- </node>
732
- <node id="n26">
733
- <data key="d5"/>
734
- <data key="d6">
735
- <y:ShapeNode>
736
- <y:Geometry height="21.0" width="21.0" x="583.6550698020917" y="793.9819121172474"/>
737
- <y:Fill hasColor="false" transparent="false"/>
738
- <y:BorderStyle color="#FFFFFF" type="line" width="1.0"/>
739
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="8.5" y="8.5">
740
- <y:LabelModel>
741
- <y:SmartNodeLabelModel distance="4.0"/>
742
- </y:LabelModel>
743
- <y:ModelParameter>
744
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
745
- </y:ModelParameter>
746
- </y:NodeLabel>
747
- <y:Shape type="ellipse"/>
748
- </y:ShapeNode>
749
- </data>
750
- </node>
751
- <node id="n27">
752
- <data key="d5"/>
753
- <data key="d6">
754
- <y:ShapeNode>
755
- <y:Geometry height="10.0" width="87.11275724687482" x="553.3710698020918" y="708.7355121172473"/>
756
- <y:Fill hasColor="false" transparent="false"/>
757
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
758
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="4.0" x="41.55637862343747" y="3.0"/>
759
- <y:Shape type="rectangle"/>
760
- </y:ShapeNode>
761
- </data>
762
- </node>
763
- <node id="n28">
764
- <data key="d5"/>
765
- <data key="d6">
766
- <y:ShapeNode>
767
- <y:Geometry height="137.0" width="101.0" x="543.6550698020919" y="649.9819121172475"/>
768
- <y:Fill color="#FFFFFF" transparent="false"/>
769
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
770
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="48.5" y="66.5">
771
- <y:LabelModel>
772
- <y:SmartNodeLabelModel distance="4.0"/>
773
- </y:LabelModel>
774
- <y:ModelParameter>
775
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
776
- </y:ModelParameter>
777
- </y:NodeLabel>
778
- <y:Shape type="rectangle"/>
779
- </y:ShapeNode>
780
- </data>
781
- </node>
782
- <node id="n29">
783
- <data key="d5"/>
784
- <data key="d6">
785
- <y:ShapeNode>
786
- <y:Geometry height="58.736959999999954" width="82.94893019790811" x="552.6806047031379" y="668.5"/>
787
- <y:Fill hasColor="false" transparent="false"/>
788
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
789
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" modelName="custom" textColor="#000000" visible="true" width="58.99609375" x="11.976418223954056" y="20.384104999999977">RaceMap<y:LabelModel>
790
- <y:SmartNodeLabelModel distance="4.0"/>
791
- </y:LabelModel>
792
- <y:ModelParameter>
793
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
794
- </y:ModelParameter>
795
- </y:NodeLabel>
796
- <y:Shape type="rectangle"/>
797
- </y:ShapeNode>
798
- </data>
799
- </node>
800
- <node id="n30">
801
- <data key="d5"/>
802
- <data key="d6">
803
- <y:ShapeNode>
804
- <y:Geometry height="116.01600000000008" width="101.0" x="694.5" y="450.0"/>
805
- <y:Fill color="#99CCFF" transparent="false"/>
806
- <y:BorderStyle hasColor="false" type="line" width="1.0"/>
807
- <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#3366FF" visible="true" width="4.0" x="48.5" y="56.00800000000004">
808
- <y:LabelModel>
809
- <y:SmartNodeLabelModel distance="4.0"/>
810
- </y:LabelModel>
811
- <y:ModelParameter>
812
- <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
813
- </y:ModelParameter>
814
- </y:NodeLabel>
815
- <y:Shape type="rectangle"/>
816
- </y:ShapeNode>
817
- </data>
818
- </node>
819
- <edge id="e0" source="n1::n0" target="n6::n0">
820
- <data key="d9"/>
821
- <data key="d10">
822
- <y:PolyLineEdge>
823
- <y:Path sx="0.0" sy="0.0" tx="-2.2737367544323206E-13" ty="-1.1368683772161603E-13"/>
824
- <y:LineStyle color="#000000" type="line" width="1.0"/>
825
- <y:Arrows source="none" target="standard"/>
826
- <y:BendStyle smoothed="false"/>
827
- </y:PolyLineEdge>
828
- </data>
829
- </edge>
830
- <edge id="e1" source="n11" target="n11">
831
- <data key="d9"/>
832
- <data key="d10">
833
- <y:QuadCurveEdge straightness="0.1">
834
- <y:Path sx="0.0" sy="0.0" tx="-24.576000000000022" ty="4.096000000000004">
835
- <y:Point x="769.2903630120961" y="530.3766478827524"/>
836
- <y:Point x="751.6775630120961" y="494.3318478827525"/>
837
- <y:Point x="791.4087630120961" y="488.5974478827525"/>
838
- <y:Point x="784.8551630120961" y="461.15424788275243"/>
839
- <y:Point x="741.8471630120961" y="467.7078478827525"/>
840
- <y:Point x="745.9431630120961" y="501.7046478827524"/>
841
- <y:Point x="719.319163012096" y="513.5830478827525"/>
842
- <y:Point x="753.3159630120961" y="510.3062478827525"/>
843
- <y:Point x="746.3527630120961" y="475.88560000000007"/>
844
- <y:Point x="796.7335630120962" y="468.11744788275246"/>
845
- <y:Point x="783.6263630120961" y="515.6310478827525"/>
846
- </y:Path>
847
- <y:LineStyle color="#000000" type="line" width="1.0"/>
848
- <y:Arrows source="none" target="none"/>
849
- </y:QuadCurveEdge>
850
- </data>
851
- </edge>
852
- <edge id="e2" source="n6::n0" target="n15">
853
- <data key="d9"/>
854
- <data key="d10">
855
- <y:PolyLineEdge>
856
- <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
857
- <y:LineStyle color="#000000" type="line" width="1.0"/>
858
- <y:Arrows source="none" target="standard"/>
859
- <y:BendStyle smoothed="false"/>
860
- </y:PolyLineEdge>
861
- </data>
862
- </edge>
863
- <edge id="e3" source="n13" target="n24">
864
- <data key="d9"/>
865
- <data key="d10">
866
- <y:PolyLineEdge>
867
- <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
868
- <y:LineStyle color="#000000" type="line" width="1.0"/>
869
- <y:Arrows source="none" target="standard"/>
870
- <y:BendStyle smoothed="false"/>
871
- </y:PolyLineEdge>
872
- </data>
873
- </edge>
874
- <edge id="e4" source="n15" target="n13">
875
- <data key="d9"/>
876
- <data key="d10">
877
- <y:PolyLineEdge>
878
- <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
879
- <y:Point x="745.0" y="628.1400000000001"/>
880
- <y:Point x="442.0" y="628.1400000000001"/>
881
- </y:Path>
882
- <y:LineStyle color="#000000" type="line" width="1.0"/>
883
- <y:Arrows source="none" target="standard"/>
884
- <y:BendStyle smoothed="false"/>
885
- </y:PolyLineEdge>
886
- </data>
887
- </edge>
888
- </graph>
889
- <data key="d0">
890
- <y:Resources/>
891
- </data>
892
-</graphml>
wiki/racelog-tracking/app-spec/training.png
... ...
Binary files /dev/null and b/wiki/racelog-tracking/app-spec/training.png differ
wiki/racelog-tracking/app-spec/training.svg
... ...
@@ -1,123 +0,0 @@
1
-<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill-opacity="1" color-rendering="auto" color-interpolation="auto" stroke="black" text-rendering="auto" stroke-linecap="square" width="466" stroke-miterlimit="10" stroke-opacity="1" shape-rendering="auto" fill="black" stroke-dasharray="none" font-weight="normal" stroke-width="1" height="425" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12" stroke-dashoffset="0" image-rendering="auto">
2
- <!--Generated by ySVG 2.5-->
3
- <defs id="genericDefs"/>
4
- <g>
5
- <defs id="defs1">
6
- <clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
7
- <path d="M0 0 L466 0 L466 425 L0 425 L0 0 Z"/>
8
- </clipPath>
9
- <clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
10
- <path d="M356 410 L822 410 L822 835 L356 835 L356 410 Z"/>
11
- </clipPath>
12
- </defs>
13
- <g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="translate(-356,-410)" stroke="white">
14
- <rect x="356" width="466" height="425" y="410" clip-path="url(#clipPath2)" stroke="none"/>
15
- </g>
16
- <g font-size="10" stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" text-rendering="geometricPrecision" font-family="sans-serif" shape-rendering="geometricPrecision" stroke-miterlimit="1.45">
17
- <text x="421.9159" xml:space="preserve" y="717.2155" clip-path="url(#clipPath2)" stroke="none">Statistics</text>
18
- </g>
19
- <g shape-rendering="geometricPrecision" text-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)">
20
- <rect x="386" y="444" clip-path="url(#clipPath2)" width="112" rx="4" ry="4" height="176" stroke="none"/>
21
- <rect stroke-linecap="butt" x="386" y="444" clip-path="url(#clipPath2)" fill="none" width="112" rx="4" ry="4" height="176" stroke-miterlimit="1.45"/>
22
- <rect x="391.5" y="450" clip-path="url(#clipPath2)" fill="white" width="101" height="137" stroke="none"/>
23
- </g>
24
- <g stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="white" stroke-miterlimit="1.45">
25
- <circle fill="none" r="10.5" clip-path="url(#clipPath2)" cx="442" cy="604.5"/>
26
- <rect x="398.5" y="475" clip-path="url(#clipPath2)" fill="none" width="87" height="16" stroke="black"/>
27
- <rect x="398.5" y="496.5" clip-path="url(#clipPath2)" fill="none" width="87" height="16" stroke="black"/>
28
- </g>
29
- <g fill="silver" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="silver">
30
- <rect x="417.5" width="49" height="16" y="520" clip-path="url(#clipPath2)" stroke="none"/>
31
- </g>
32
- <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" font-family="sans-serif" transform="matrix(1,0,0,1,-356,-410)" stroke-linecap="butt">
33
- <text x="425.708" xml:space="preserve" y="532.1543" clip-path="url(#clipPath2)" stroke="none">Login</text>
34
- <rect fill="none" x="417.5" width="49" height="16" y="520" clip-path="url(#clipPath2)"/>
35
- <text x="409.3608" xml:space="preserve" font-size="10" y="562.088" clip-path="url(#clipPath2)" stroke="none">already have</text>
36
- <text x="411.6045" xml:space="preserve" font-size="10" y="573.7286" clip-path="url(#clipPath2)" stroke="none">an account?</text>
37
- <rect stroke-linecap="square" x="537.5" y="444" clip-path="url(#clipPath2)" width="112" rx="4" ry="4" height="176" stroke="none" stroke-miterlimit="10"/>
38
- <rect x="537.5" y="444" clip-path="url(#clipPath2)" fill="none" width="112" rx="4" ry="4" height="176"/>
39
- </g>
40
- <g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="white">
41
- <rect x="543" width="101" height="137" y="450" clip-path="url(#clipPath2)" stroke="none"/>
42
- <circle stroke-linecap="butt" clip-path="url(#clipPath2)" fill="none" r="10.5" cx="593.5" cy="604.5" stroke-miterlimit="1.45"/>
43
- <rect x="698.9988" y="568.7536" clip-path="url(#clipPath2)" fill="silver" width="92.33" height="15.4634" stroke="none"/>
44
- </g>
45
- <g font-size="10" stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" text-rendering="geometricPrecision" font-family="sans-serif" shape-rendering="geometricPrecision" stroke-miterlimit="1.45">
46
- <text x="710.6569" xml:space="preserve" y="579.9472" clip-path="url(#clipPath2)" stroke="none">Stop Tracking</text>
47
- <rect fill="none" x="698.9988" width="92.33" height="15.4634" y="568.7536" clip-path="url(#clipPath2)"/>
48
- <path fill="none" d="M498.0358 532 L529.5173 532" clip-path="url(#clipPath2)"/>
49
- <path d="M537.5173 532 L525.5173 527 L528.5173 532 L525.5173 537 Z" clip-path="url(#clipPath2)" stroke="none"/>
50
- </g>
51
- <g fill="silver" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="silver">
52
- <rect x="546.8288" width="93" height="16" y="475.2032" clip-path="url(#clipPath2)" stroke="none"/>
53
- </g>
54
- <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke-linecap="butt">
55
- <rect fill="none" x="546.8288" width="93" height="16" y="475.2032" clip-path="url(#clipPath2)"/>
56
- <text x="567.4633" font-size="10" y="486.6651" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" xml:space="preserve">Start Tracking</text>
57
- </g>
58
- <g fill="red" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="red">
59
- <circle r="7.4634" clip-path="url(#clipPath2)" cx="554.9621" cy="483.2032" stroke="none"/>
60
- <rect x="547.4988" y="515.096" clip-path="url(#clipPath2)" fill="silver" width="92.33" height="15.4634" stroke="none"/>
61
- </g>
62
- <g font-size="10" stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" text-rendering="geometricPrecision" font-family="sans-serif" shape-rendering="geometricPrecision" stroke-miterlimit="1.45">
63
- <text x="568.3781" xml:space="preserve" y="526.2896" clip-path="url(#clipPath2)" stroke="none">My Tracks</text>
64
- <rect fill="none" x="547.4988" width="92.33" height="15.4634" y="515.096" clip-path="url(#clipPath2)"/>
65
- </g>
66
- <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="rgb(153,204,255)">
67
- <rect x="694.5" width="101" height="116.016" y="450" clip-path="url(#clipPath2)" stroke="none"/>
68
- </g>
69
- <g font-size="10" stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" text-rendering="geometricPrecision" font-family="sans-serif" shape-rendering="geometricPrecision" stroke-miterlimit="1.45">
70
- <text x="417.6227" xml:space="preserve" y="719.5582" clip-path="url(#clipPath2)" stroke="none">Statistics:</text>
71
- <text x="437.3957" xml:space="preserve" y="731.1989" clip-path="url(#clipPath2)" stroke="none">...</text>
72
- <text x="437.3957" xml:space="preserve" y="742.8395" clip-path="url(#clipPath2)" stroke="none">...</text>
73
- </g>
74
- <g shape-rendering="geometricPrecision" text-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)">
75
- <rect x="386" y="644" clip-path="url(#clipPath2)" width="112" rx="4" ry="4" height="176" stroke="none"/>
76
- <rect stroke-linecap="butt" x="386" y="644" clip-path="url(#clipPath2)" fill="none" width="112" rx="4" ry="4" height="176" stroke-miterlimit="1.45"/>
77
- <rect x="391.5" y="650" clip-path="url(#clipPath2)" fill="white" width="101" height="137" stroke="none"/>
78
- <rect x="689" y="444" clip-path="url(#clipPath2)" width="112" rx="4" ry="4" height="176" stroke="none"/>
79
- <rect stroke-linecap="butt" x="689" y="444" clip-path="url(#clipPath2)" fill="none" width="112" rx="4" ry="4" height="176" stroke-miterlimit="1.45"/>
80
- <rect x="694.5" y="450" clip-path="url(#clipPath2)" fill="white" width="101" height="137" stroke="none"/>
81
- </g>
82
- <g stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="white" stroke-miterlimit="1.45">
83
- <circle fill="none" r="10.5" clip-path="url(#clipPath2)" cx="745" cy="604.5"/>
84
- <circle fill="none" r="10.5" clip-path="url(#clipPath2)" cx="442" cy="804.5"/>
85
- </g>
86
- <g fill="silver" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="silver">
87
- <rect x="395.9988" width="92.33" height="15.4634" y="768.7536" clip-path="url(#clipPath2)" stroke="none"/>
88
- </g>
89
- <g font-size="10" stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" text-rendering="geometricPrecision" font-family="sans-serif" shape-rendering="geometricPrecision" stroke-miterlimit="1.45">
90
- <text x="422.4616" xml:space="preserve" y="779.9472" clip-path="url(#clipPath2)" stroke="none">Analyze</text>
91
- <rect fill="none" x="395.9988" width="92.33" height="15.4634" y="768.7536" clip-path="url(#clipPath2)"/>
92
- </g>
93
- <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="rgb(153,204,255)">
94
- <rect x="395.9988" width="92.33" height="46" y="654.9152" clip-path="url(#clipPath2)" stroke="none"/>
95
- <rect x="698.9988" y="568.7536" clip-path="url(#clipPath2)" fill="silver" width="92.33" height="15.4634" stroke="none"/>
96
- </g>
97
- <g font-size="10" stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" text-rendering="geometricPrecision" font-family="sans-serif" shape-rendering="geometricPrecision" stroke-miterlimit="1.45">
98
- <text x="710.6569" xml:space="preserve" y="579.9472" clip-path="url(#clipPath2)" stroke="none">Stop Tracking</text>
99
- <rect fill="none" x="698.9988" width="92.33" height="15.4634" y="568.7536" clip-path="url(#clipPath2)"/>
100
- </g>
101
- <g shape-rendering="geometricPrecision" text-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)">
102
- <rect x="538.1551" y="643.9819" clip-path="url(#clipPath2)" width="112" rx="4" ry="4" height="176" stroke="none"/>
103
- <rect stroke-linecap="butt" x="538.1551" y="643.9819" clip-path="url(#clipPath2)" fill="none" width="112" rx="4" ry="4" height="176" stroke-miterlimit="1.45"/>
104
- </g>
105
- <g stroke-linecap="butt" transform="matrix(1,0,0,1,-356,-410)" fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="white" stroke-miterlimit="1.45">
106
- <circle fill="none" r="10.5" clip-path="url(#clipPath2)" cx="594.1551" cy="804.4819"/>
107
- <rect stroke-linecap="square" x="543.6551" y="649.9819" clip-path="url(#clipPath2)" width="101" height="137" stroke="none" stroke-miterlimit="10"/>
108
- <text x="566.657" y="702.0228" clip-path="url(#clipPath2)" fill="black" font-family="sans-serif" stroke="none" xml:space="preserve">RaceMap</text>
109
- </g>
110
- <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke="rgb(153,204,255)">
111
- <rect x="694.5" width="101" height="116.016" y="450" clip-path="url(#clipPath2)" stroke="none"/>
112
- </g>
113
- <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-356,-410)" stroke-linecap="butt">
114
- <path fill="none" d="M741.6555 538.0825 L750.1211 535.7219 L754.4901 534.2157 L758.0123 532.3696 L760.6878 530.1837 L762.5166 527.658 L763.4987 524.7924 L763.634 521.5869 L762.9227 518.0416 L761.3646 514.1565 L759.6033 510.552 L758.025 506.7101 L757.2532 503.2944 L757.2878 500.305 L758.1288 497.7418 L759.7761 495.6048 L762.2299 493.8941 L765.4901 492.6096 L769.5566 491.7513 L773.5297 491.1779 L781.1674 489.2771 L784.01 487.8688 L786.2017 486.1552 L787.7425 484.1363 L788.6324 481.8122 L788.8715 479.1827 L788.4596 476.248 L787.8043 473.5037 L786.8107 470.6554 L785.3044 468.2851 L783.2856 466.393 L780.754 464.9789 L777.7099 464.0429 L774.1531 463.585 L765.5016 464.1034 L761.2008 464.7587 L756.6936 465.689 L752.8488 467.0051 L749.6664 468.7072 L747.1464 470.7952 L745.2888 473.2691 L744.0936 476.129 L743.5608 479.3747 L743.6904 483.0064 L744.1 486.4061 L744.1576 493.4333 L743.5384 496.4803 L742.4872 499.2163 L741.004 501.6413 L739.0888 503.7552 L733.9624 507.0499 L731.2999 508.2378 L727.0145 510.4842 L726.1505 511.2877 L726.139 511.8781 L728.6734 512.4196 L734.6177 512.1085 L738.0174 511.7808 L741.554 511.1932 L744.5147 510.1676 L746.8994 508.7041 L748.7079 506.8026 L749.9406 504.4631 L750.5972 501.6857 L750.6779 498.4703 L750.1825 494.817 L749.4862 491.3749 L749.1061 487.6899 L749.5323 484.3798 L750.765 481.4445 L752.804 478.884 L755.6494 476.6983 L759.3013 474.8874 L763.7595 473.4513 L769.0242 472.39 L774.0623 471.6131 L779.2837 471.1279 L783.6123 471.4201 L787.0482 472.4897 L789.5912 474.3366 L791.2414 476.961 L791.9988 480.3628 L791.8635 484.542 L790.8353 489.4986 L789.5246 494.2499 L787.5691 499.4771 L784.6516 504.4682 L780.7723 509.2231 L775.931 513.7418 L770.1278 518.0244 L763.3627 522.0707 L755.6356 525.881 L746.9467 529.4551 L702.116 546.3511" clip-path="url(#clipPath2)"/>
115
- <path fill="none" d="M649.5358 532 L681.0173 532" clip-path="url(#clipPath2)"/>
116
- <path d="M689.0173 532 L677.0173 527 L680.0173 532 L677.0173 537 Z" clip-path="url(#clipPath2)" stroke="none"/>
117
- <path fill="none" d="M745 620.0188 L745 628.14 L442 628.14 L442 635.9878" clip-path="url(#clipPath2)"/>
118
- <path d="M442 643.9878 L447 631.9878 L442 634.9878 L437 631.9878 Z" clip-path="url(#clipPath2)" stroke="none"/>
119
- <path fill="none" d="M497.9809 731.9933 L530.1317 731.9895" clip-path="url(#clipPath2)"/>
120
- <path d="M538.1317 731.9886 L526.1312 726.99 L529.1317 731.9896 L526.1323 736.9899 Z" clip-path="url(#clipPath2)" stroke="none"/>
121
- </g>
122
- </g>
123
-</svg>
wiki/uploading-media-content.md
... ...
@@ -0,0 +1,31 @@
1
+# Uploading Media Content
2
+
3
+We have a place to store media content such as images and videos, e.g., created at sailing events. This place is the directory `/var/www/static` on our web server, and it is publicly reachable by HTTP at `http://static.sapsailing.com/...`. For example, the file `/var/www/static/images/splash-screen-compressed-02.png` is reachable via the URL `http://static.sapsailing.com/images/splash-screen-compressed-02.png`.
4
+
5
+Uploading content to this directory works best using the "secure copy" (SCP) protocol, sometimes also referred to as SFTP. It is basically sending files through a secure shell (SSH) connection. Command line aficionados will know the "scp" command and its options and will understand what it means to have a public/private key pair that allows them to log on to the `sapsailing.com` web server.
6
+
7
+For those who don't, we can easily create username/password accounts on the web server which spares the technical tidbits of public/private key pair creation and registration. A good graphical tool (for the less command line-oriented folks) is WinSCP which can be downloaded at [http://winscp.net](http://winscp.net/eng/download.php#download2).
8
+
9
+For those in an open Internet environment (outside the SAP VPN or logged on to the "SAP Internet" WiFi) the hostname to use is `sapsailing.com` and the port number is `22`. Inside the SAP VPN we have to use a little trick to make the connection. Use `10.18.22.50` as the hostname and `12349` as the port number.
10
+
11
+## Quick Introduction to the Use of WinSCP
12
+
13
+After downloading WinSCP from the URL shown above, run the installer. When it asks you about your preferred user interface style you may want to choose "Explorer" which may be most familiar for Windows users.
14
+
15
+![WinSCP Install](/wiki/images/winscp/winscp-install-1.png)
16
+
17
+Then, launch WinSCP. It welcomes you with a login screen:
18
+
19
+![WinSCP Login](/wiki/images/winscp/winscp-login.png)
20
+
21
+Enter the hostname and port number as described above, depending on your network environment (within SAP network or outside of SAP network). Add your user name and password, then click the "Login" button. You should then see an Explorer-like window that shows the web server's file system hierarchy.
22
+
23
+Navigate to /var/www/static:
24
+
25
+![WinSCP /var/www/static](/wiki/images/winscp/winscp-var-www-static.png)
26
+
27
+You can create new folders using the "Create Folder" button from the toolbar:
28
+
29
+![WinSCP Create Folder](/wiki/images/winscp/winscp-create-folder.png)
30
+
31
+Files can be uploaded from your local disk by dragging and dropping them from a local Windows Explorer running on your desktop onto the content panel in the right part of the WinSCP window.
... ...
\ No newline at end of file