Version 2.0: Added UUID to markers, truncated timestamps to seconds field.
* Added a UUID field to TimelineMarker. * Updated StreamBasedTimelineSource to read and write UUIDs. The current format is compatible with the 1.x format, but support for the 1.x format is planned to be deprecated for 3.x. The UUID is added on the same line as the timestamp, with a `,` separating the timestamp and the UUID value. * Updated JDBLabsWebTimelineSource to use UUIDs to reconcile the different timestamp entries (more reliable that using the timestamp value).
This commit is contained in:
parent
4c8d8a9f2d
commit
d720c6c645
@ -1,5 +1,5 @@
|
||||
#Sun, 22 Sep 2013 14:58:43 -0500
|
||||
#Fri, 11 Oct 2013 18:08:21 +0000
|
||||
name=timestamper-lib
|
||||
version=1.5
|
||||
version=2.0
|
||||
lib.local=true
|
||||
build.number=1
|
||||
|
@ -3,6 +3,7 @@ package com.jdblabs.timestamper.core
|
||||
import com.jdbernard.util.SmartConfig
|
||||
import groovyx.net.http.HTTPBuilder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.UUID;
|
||||
|
||||
import static groovyx.net.http.ContentType.*
|
||||
import static groovyx.net.http.Method.*
|
||||
@ -27,7 +28,7 @@ public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||
private URI baseUri
|
||||
private HTTPBuilder http
|
||||
|
||||
private Map entryHashes
|
||||
private Map serverEntryIds
|
||||
|
||||
public JDBLabsWebTimelineSource(URI uri, SmartConfig config) {
|
||||
super(uri)
|
||||
@ -50,8 +51,8 @@ public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||
"communicating to the web timeline.\n" +
|
||||
"${resp.statusLine}: ${json}") }
|
||||
|
||||
// init our hash of known entries
|
||||
entryHashes = [:]
|
||||
// init our map of known entries
|
||||
serverEntryIds = [:]
|
||||
}
|
||||
|
||||
public Timeline read() {
|
||||
@ -76,11 +77,12 @@ public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||
|
||||
timeline = new Timeline()
|
||||
|
||||
// create start and end times
|
||||
// create start and end times to use as parameters to the web service
|
||||
startDate = Calendar.getInstance()
|
||||
startDate.timeInMillis = 0
|
||||
startDate.timeInMillis = 0 // Beginning of the epoch
|
||||
startDate = isoDateFormat.format(startDate.time)
|
||||
|
||||
// Until now
|
||||
endDate = isoDateFormat.format(Calendar.getInstance().time)
|
||||
|
||||
// load the timeline entries
|
||||
@ -96,11 +98,12 @@ public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||
|
||||
// parse and create the timeline marker
|
||||
def timestamp = isoDateFormat.parse(entry.timestamp)
|
||||
def marker = new TimelineMarker(timestamp, entry.mark, entry.notes)
|
||||
def marker = new TimelineMarker(timestamp, entry.mark, entry.notes,
|
||||
UUID.fromString(entry.uuid));
|
||||
|
||||
// add it to the timeline and our map of hashes
|
||||
timeline.addMarker(marker)
|
||||
entryHashes[fullHash(marker)] = entry.id
|
||||
serverEntryIds[marker.uuid] = entry.id
|
||||
}
|
||||
|
||||
// return the created timeline
|
||||
@ -114,28 +117,38 @@ public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||
// make sure we have a fresh session
|
||||
authenticate()
|
||||
|
||||
// find differences since we last persisted
|
||||
deletedEntries = entryHashes.clone() // shallow copy
|
||||
// Find differences since we last persisted.
|
||||
|
||||
// We want to identify the set of entries that were previously present
|
||||
// on the server but are no longer present locally and delete them from
|
||||
// the server. We start with the set of all entries last seen on the
|
||||
// server. We look through our local entries and if we find the entry
|
||||
// locally we remove it from our set of deletion candidates.
|
||||
|
||||
// We will also be constructing a set of entries that we have locally
|
||||
// which were not present on the server. This set of entries will be
|
||||
// added to the server.
|
||||
|
||||
deletedEntries = serverEntryIds.clone() // shallow copy
|
||||
newEntries = []
|
||||
|
||||
t.each { marker ->
|
||||
def hash = fullHash(marker)
|
||||
// This marker is present on the server and is still present
|
||||
// locally, remove from deletedEntries
|
||||
if (deletedEntries.containsKey(marker.uuid)) {
|
||||
deletedEntries.remove(marker.uuid) }
|
||||
|
||||
// this marker is present, remove from deletedEntries
|
||||
if (deletedEntries.containsKey(hash)) {
|
||||
deletedEntries.remove(hash) }
|
||||
|
||||
// this marker is not present, add to newEntries
|
||||
// This marker is not present on the server, add to newEntries.
|
||||
else { newEntries << marker }
|
||||
}
|
||||
|
||||
// delete all entries that used to be present but are not any longer
|
||||
// Delete all entries that used to be present but are not any longer
|
||||
deletedEntries.each { hash, entryId ->
|
||||
http.request(DELETE) {
|
||||
uri.path = "entries/${username}/${timelineId}/${entryId}"
|
||||
|
||||
response.'404' = { resp -> entryHashes.remove(hash) }
|
||||
response.success = { resp -> entryHashes.remove(hash) }
|
||||
response.'404' = { resp -> serverEntryIds.remove(hash) }
|
||||
response.success = { resp -> serverEntryIds.remove(hash) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,6 +158,7 @@ public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||
def entryBody = [
|
||||
mark: entry.mark,
|
||||
notes: entry.notes,
|
||||
uuid: entry.uuid.toString(),
|
||||
timestamp: isoDateFormat.format(entry.timestamp)
|
||||
]
|
||||
|
||||
@ -154,7 +168,7 @@ public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||
body = entryBody
|
||||
|
||||
response.success = { resp, json ->
|
||||
entryHashes.put(fullHash(entry), json?.id ?: 0) }
|
||||
serverEntryIds.put(entry.uuid, json?.id ?: 0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.io.Writer;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -71,8 +72,9 @@ public class StreamBasedTimelineSource extends TimelineSource {
|
||||
Writer out = new OutputStreamWriter(stream);
|
||||
for (TimelineMarker tm : timeline) {
|
||||
|
||||
// write timestamp
|
||||
out.write(Timeline.longFormat.format(tm.getTimestamp()) + "\n");
|
||||
// write timestamp and UUID
|
||||
out.write(Timeline.longFormat.format(tm.getTimestamp()) + "," +
|
||||
tm.getUuid().toString() + "\n");
|
||||
|
||||
// write mark
|
||||
String mark = tm.getMark().replace('\n', '\u0000');
|
||||
@ -127,7 +129,8 @@ public class StreamBasedTimelineSource extends TimelineSource {
|
||||
null : new PrintWriter(commentStream);
|
||||
|
||||
ReadingState readingState = ReadingState.NewMarker;
|
||||
Date d = null;
|
||||
Date date = null;
|
||||
UUID uuid = null;
|
||||
StringBuilder mark = null;
|
||||
StringBuilder notes = null;
|
||||
String line;
|
||||
@ -146,11 +149,24 @@ public class StreamBasedTimelineSource extends TimelineSource {
|
||||
switch (readingState) {
|
||||
|
||||
case NewMarker:
|
||||
try { d = Timeline.longFormat.parse(line); }
|
||||
try {
|
||||
String[] parts = line.split(",");
|
||||
date = Timeline.longFormat.parse(parts[0]);
|
||||
|
||||
// If there is no UUID, we will ignore it. This allows us
|
||||
// to support timeline files generated by 1.x versions of
|
||||
// this library.
|
||||
// TODO: Remove this check in version 3.x
|
||||
if (parts.length > 1) uuid = UUID.fromString(parts[1]);
|
||||
else uuid = null; }
|
||||
catch (ParseException pe) {
|
||||
throw new IOException("Error parsing timeline file at line "
|
||||
+ lineNumber + ": expected a new marker date but could not parse"
|
||||
+ " the date. Error: " + pe.getLocalizedMessage()); }
|
||||
catch (IllegalArgumentException iae) {
|
||||
throw new IOException("Error parsing timeline file at line "
|
||||
+ lineNumber + ": Expected a UUID but could not parse "
|
||||
+ "the value. Details: " + iae.getLocalizedMessage()); }
|
||||
readingState = ReadingState.StartMark;
|
||||
break;
|
||||
|
||||
@ -179,7 +195,13 @@ public class StreamBasedTimelineSource extends TimelineSource {
|
||||
case EndMarker:
|
||||
String sMark = mark.toString().replace('\u0000', '\n');
|
||||
String sNotes = notes.toString().replace('\u0000', '\n');
|
||||
timeline.addMarker(d, sMark, sNotes);
|
||||
TimelineMarker marker;
|
||||
|
||||
// Support a missing UUID until version 3.x
|
||||
if (uuid == null)
|
||||
marker = new TimelineMarker(date, sMark, sNotes);
|
||||
else marker = new TimelineMarker(date, sMark, sNotes, uuid);
|
||||
timeline.addMarker(marker);
|
||||
readingState = ReadingState.NewMarker; } }
|
||||
|
||||
return timeline; }
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Jonathan Bernard {@literal <jdbernard@gmail.com>}
|
||||
@ -11,16 +12,23 @@ public class TimelineMarker implements Comparable<TimelineMarker> {
|
||||
|
||||
private final Date timestamp;
|
||||
private final String mark;
|
||||
private final UUID uuid;
|
||||
private String notes;
|
||||
|
||||
public TimelineMarker(Date timestamp, String mark, String notes) {
|
||||
this(timestamp, mark, notes, UUID.randomUUID()); }
|
||||
|
||||
public TimelineMarker(Date timestamp, String mark, String notes, UUID uuid) {
|
||||
if (timestamp == null || mark == null)
|
||||
throw new IllegalArgumentException("Null timestamp or mark"
|
||||
+ " is not permitted.");
|
||||
|
||||
this.timestamp = timestamp;
|
||||
// We truncate milliseconds.
|
||||
this.timestamp = 1000 * (timestamp / 1000);
|
||||
|
||||
this.mark = mark;
|
||||
this.notes = notes;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public Date getTimestamp() { return timestamp; }
|
||||
@ -29,12 +37,19 @@ public class TimelineMarker implements Comparable<TimelineMarker> {
|
||||
|
||||
public String getNotes() { return notes; }
|
||||
|
||||
public UUID getUuid() { return uuid; }
|
||||
|
||||
public void setNotes(String notes) { this.notes = notes; }
|
||||
|
||||
@Override
|
||||
public int compareTo(TimelineMarker that) {
|
||||
// Always greater than null
|
||||
if (that == null) return Integer.MAX_VALUE;
|
||||
|
||||
// Always equal to other instances of itself (same UUID).
|
||||
if (this.uuid.equals(that.uuid)) return 0;
|
||||
|
||||
// Check the timestamp, then the mark if the timestamps are equal.
|
||||
int val = this.timestamp.compareTo(that.timestamp);
|
||||
if (val == 0) val = this.mark.compareTo(that.mark);
|
||||
|
||||
@ -47,8 +62,7 @@ public class TimelineMarker implements Comparable<TimelineMarker> {
|
||||
if (!(o instanceof TimelineMarker)) return false;
|
||||
|
||||
TimelineMarker that = (TimelineMarker) o;
|
||||
return this.timestamp.equals(that.timestamp) &&
|
||||
this.mark.equals(that.mark);
|
||||
return this.uuid.equals(that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user