Version 1.1: Added support for web timelines.
Web timelines as defined by the JDB Labs Web TimeStamper tool are now supported. * `TimelineSource` now also takes the timeline config file as constructor input. * Refactored `Timeline` to use reduce implementation redundencies: internal implementations do not duplicate functionality. * Improved comments in `TimelineProperties`. * Updated `TimelineSourceFactory` to handle timelines that need to use `JDBLabsWebTimelineSource`.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
#Thu, 16 Jun 2011 12:41:01 -0500
|
#Mon, 27 Jun 2011 17:12:00 -0500
|
||||||
name=timestamper-lib
|
name=timestamper-lib
|
||||||
version=1.0
|
version=1.1
|
||||||
lib.local=true
|
lib.local=true
|
||||||
|
|
||||||
build.number=2
|
build.number=1
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
|
|||||||
|
import groovyx.net.http.HTTPBuilder
|
||||||
|
import com.jdbernard.util.SmartConfig
|
||||||
|
import com.jdblabs.timestamper.core.*
|
||||||
|
|
||||||
|
smartConfig = new SmartConfig("temp.config")
|
||||||
|
smartConfig."web.username" = "jdbernard"
|
||||||
|
smartConfig."web.password" = "Y0uthc"
|
||||||
|
smartConfig."web.timelineId" = "work"
|
||||||
|
|
||||||
|
uri = new URI("http://timestamper-local:8000")
|
||||||
|
|
||||||
|
wtls = new JDBLabsWebTimelineSource(uri, smartConfig)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.jdblabs.timestamper.core;
|
package com.jdblabs.timestamper.core;
|
||||||
|
|
||||||
|
import com.jdbernard.util.SmartConfig;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -15,7 +16,7 @@ public class FileTimelineSource extends TimelineSource {
|
|||||||
|
|
||||||
private File file;
|
private File file;
|
||||||
|
|
||||||
public FileTimelineSource(URI uri) {
|
public FileTimelineSource(URI uri, SmartConfig config) {
|
||||||
super(uri);
|
super(uri);
|
||||||
this.file = new File(uri);
|
this.file = new File(uri);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package com.jdblabs.timestamper.core
|
||||||
|
|
||||||
|
import com.jdbernard.util.SmartConfig
|
||||||
|
import groovyx.net.http.HTTPBuilder
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
import static groovyx.net.http.ContentType.*
|
||||||
|
import static groovyx.net.http.Method.*
|
||||||
|
|
||||||
|
public class JDBLabsWebTimelineSource extends TimelineSource {
|
||||||
|
|
||||||
|
private static final String CONFIG_UNAME = "web.username"
|
||||||
|
private static final String CONFIG_PWD = "web.password"
|
||||||
|
private static final String CONFIG_TIMELINE = "web.timelineId"
|
||||||
|
|
||||||
|
private static isoDateFormat
|
||||||
|
|
||||||
|
static {
|
||||||
|
isoDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||||
|
isoDateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
|
||||||
|
private String username
|
||||||
|
private String password
|
||||||
|
private String timelineId
|
||||||
|
|
||||||
|
private URI baseUri
|
||||||
|
private HTTPBuilder http
|
||||||
|
|
||||||
|
private Map entryHashes
|
||||||
|
|
||||||
|
public JDBLabsWebTimelineSource(URI uri, SmartConfig config) {
|
||||||
|
super(uri)
|
||||||
|
|
||||||
|
// load web-timeline specific properties
|
||||||
|
username = config.getProperty(CONFIG_UNAME, "")
|
||||||
|
password = config.getProperty(CONFIG_PWD, "")
|
||||||
|
timelineId = config.getProperty(CONFIG_TIMELINE, "")
|
||||||
|
|
||||||
|
baseUri = new URI(uri.scheme + "://" + uri.authority)
|
||||||
|
http = new HTTPBuilder(baseUri)
|
||||||
|
|
||||||
|
entryHashes = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timeline read() {
|
||||||
|
def timelineJSON
|
||||||
|
def entryListJSON
|
||||||
|
def startDate, endDate
|
||||||
|
Timeline timeline
|
||||||
|
|
||||||
|
// make sure we have a fresh session
|
||||||
|
authenticate()
|
||||||
|
|
||||||
|
// load the timeline information
|
||||||
|
timelineJSON = http.get(
|
||||||
|
path: "/ts_api/timelines/${username}/${timelineId}",
|
||||||
|
contentType: JSON) { resp, json -> json }
|
||||||
|
|
||||||
|
timeline = new Timeline()
|
||||||
|
|
||||||
|
// create start and end times
|
||||||
|
startDate = Calendar.getInstance()
|
||||||
|
startDate.timeInMillis = 0
|
||||||
|
startDate = isoDateFormat.format(startDate.time)
|
||||||
|
|
||||||
|
endDate = isoDateFormat.format(Calendar.getInstance().time)
|
||||||
|
|
||||||
|
// load the timeline entries
|
||||||
|
entryListJSON = http.get(
|
||||||
|
path: "/ts_api/entries/${username}/${timelineId}",
|
||||||
|
contentType: JSON,
|
||||||
|
query: [
|
||||||
|
byDate: true,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate ] ) { resp, json -> json }
|
||||||
|
|
||||||
|
entryListJSON.each { entry ->
|
||||||
|
|
||||||
|
// parse and create the timeline marker
|
||||||
|
def timestamp = isoDateFormat.parse(entry.timestamp)
|
||||||
|
def marker = new TimelineMarker(timestamp, entry.mark, entry.notes)
|
||||||
|
|
||||||
|
// add it to the timeline and our map of hashes
|
||||||
|
timeline.addMarker(marker)
|
||||||
|
entryHashes[fullHash(marker)] = entry.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the created timeline
|
||||||
|
return timeline
|
||||||
|
}
|
||||||
|
|
||||||
|
public void persist(Timeline t) {
|
||||||
|
Map deletedEntries
|
||||||
|
List newEntries
|
||||||
|
|
||||||
|
// make sure we have a fresh session
|
||||||
|
authenticate()
|
||||||
|
|
||||||
|
// find differences since we last persisted
|
||||||
|
deletedEntries = entryHashes.clone() // shallow copy
|
||||||
|
newEntries = []
|
||||||
|
|
||||||
|
t.each { marker ->
|
||||||
|
def hash = fullHash(marker)
|
||||||
|
|
||||||
|
// this marker is present, remove from deletedEntries
|
||||||
|
if (deletedEntries.containsKey(hash)) {
|
||||||
|
deletedEntries.remove(hash) }
|
||||||
|
|
||||||
|
// this marker is not present, add to newEntries
|
||||||
|
else { newEntries << marker }
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all entries that used to be present but are not any longer
|
||||||
|
deletedEntries.each { hash, entryId ->
|
||||||
|
http.request(DELETE) {
|
||||||
|
uri.path = "/ts_api/entries/${username}/${timelineId}/${entryId}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: error handling, make sure this only happens on success
|
||||||
|
entryHashes.remove(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add all new entries
|
||||||
|
newEntries.each { entry ->
|
||||||
|
|
||||||
|
def entryBody = [
|
||||||
|
mark: entry.mark,
|
||||||
|
notes: entry.notes,
|
||||||
|
timestamp: isoDateFormat.format(entry.timestamp)
|
||||||
|
]
|
||||||
|
|
||||||
|
// TODO: error handling
|
||||||
|
http.post(
|
||||||
|
path: "/ts_api/entries/${username}/${timelineId}",
|
||||||
|
contentType: JSON,
|
||||||
|
requestContentType: JSON,
|
||||||
|
body: entryBody) { resp, json ->
|
||||||
|
entryHashes.put(fullHash(entry), entry)
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authenticate() {
|
||||||
|
// TODO: error detection
|
||||||
|
http.post(
|
||||||
|
path: '/ts_api/login',
|
||||||
|
contentType: JSON,
|
||||||
|
requestContentType: JSON,
|
||||||
|
body: [ username: this.username,
|
||||||
|
password: this.password ]) { resp, json -> json }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int fullHash(TimelineMarker tm) {
|
||||||
|
return 61 * tm.hashCode() + (tm.mark != null ? tm.mark.hashCode() : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ public class Timeline implements Iterable<TimelineMarker> {
|
|||||||
|
|
||||||
public static SimpleDateFormat shortFormat = new SimpleDateFormat("HH:mm:ss");
|
public static SimpleDateFormat shortFormat = new SimpleDateFormat("HH:mm:ss");
|
||||||
public static SimpleDateFormat longFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
|
public static SimpleDateFormat longFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
|
||||||
private TreeSet<TimelineMarker> timelineList;
|
protected TreeSet<TimelineMarker> timelineList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new, empty Timeline.
|
* Create a new, empty Timeline.
|
||||||
@@ -42,7 +42,7 @@ public class Timeline implements Iterable<TimelineMarker> {
|
|||||||
* @return <code>true</code> if this Timeline was modified.
|
* @return <code>true</code> if this Timeline was modified.
|
||||||
*/
|
*/
|
||||||
public boolean addMarker(Date timestamp, String name, String notes) {
|
public boolean addMarker(Date timestamp, String name, String notes) {
|
||||||
return timelineList.add(new TimelineMarker(timestamp, name, notes));
|
return addMarker(new TimelineMarker(timestamp, name, notes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,11 +107,7 @@ public class Timeline implements Iterable<TimelineMarker> {
|
|||||||
public boolean addAll(Timeline t) {
|
public boolean addAll(Timeline t) {
|
||||||
boolean modified = false;
|
boolean modified = false;
|
||||||
for (TimelineMarker tm : t) {
|
for (TimelineMarker tm : t) {
|
||||||
if (!timelineList.contains(tm)) {
|
modified = addMarker(tm) || modified; }
|
||||||
timelineList.add(tm);
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class TimelineProperties {
|
|||||||
URI timelineURI = timelineFile.toURI();
|
URI timelineURI = timelineFile.toURI();
|
||||||
|
|
||||||
timeline = new Timeline();
|
timeline = new Timeline();
|
||||||
timelineSource = TimelineSourceFactory.newInstance(timelineURI);
|
timelineSource = TimelineSourceFactory.newInstance(timelineURI, config);
|
||||||
persistOnUpdate = true;
|
persistOnUpdate = true;
|
||||||
try { timelineSource.persist(timeline); }
|
try { timelineSource.persist(timeline); }
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
@@ -84,8 +84,8 @@ public class TimelineProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load TimelineProperties from an InputStream.
|
* Load TimelineProperties from a config file.
|
||||||
* @param is
|
* @param propFile
|
||||||
*/
|
*/
|
||||||
public TimelineProperties(File propFile) throws IOException {
|
public TimelineProperties(File propFile) throws IOException {
|
||||||
String strURI;
|
String strURI;
|
||||||
@@ -97,8 +97,10 @@ public class TimelineProperties {
|
|||||||
persistOnUpdate = (Boolean) config.getProperty(
|
persistOnUpdate = (Boolean) config.getProperty(
|
||||||
LOCAL_TIMELINE_PERSIST_ON_UPDATE, true);
|
LOCAL_TIMELINE_PERSIST_ON_UPDATE, true);
|
||||||
|
|
||||||
// load local timeline
|
// get the URI for the primary timeline
|
||||||
strURI = (String) config.getProperty(LOCAL_TIMELINE_URI, "");
|
strURI = (String) config.getProperty(LOCAL_TIMELINE_URI, "");
|
||||||
|
|
||||||
|
// no primary timeline, default to file-based timeline
|
||||||
if ("".equals(strURI)) {
|
if ("".equals(strURI)) {
|
||||||
File defaultTimelineFile = new File("timeline.default.txt");
|
File defaultTimelineFile = new File("timeline.default.txt");
|
||||||
try {
|
try {
|
||||||
@@ -108,7 +110,7 @@ public class TimelineProperties {
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
timelineURI = defaultTimelineFile.toURI();
|
timelineURI = defaultTimelineFile.toURI();
|
||||||
} else {
|
} else { // we do have a URI
|
||||||
try { timelineURI = new URI(strURI); }
|
try { timelineURI = new URI(strURI); }
|
||||||
catch (URISyntaxException urise) {
|
catch (URISyntaxException urise) {
|
||||||
throw new IOException("Unable to load the timeline: the timeline "
|
throw new IOException("Unable to load the timeline: the timeline "
|
||||||
@@ -116,7 +118,8 @@ public class TimelineProperties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineSource = TimelineSourceFactory.newInstance(timelineURI);
|
// create our timeline source and read the timeline
|
||||||
|
timelineSource = TimelineSourceFactory.newInstance(timelineURI, config);
|
||||||
timeline = timelineSource.read();
|
timeline = timelineSource.read();
|
||||||
|
|
||||||
// search keys for remote timeline entries
|
// search keys for remote timeline entries
|
||||||
@@ -143,7 +146,7 @@ public class TimelineProperties {
|
|||||||
|
|
||||||
// add a new SyncTarget to the list
|
// add a new SyncTarget to the list
|
||||||
st = new SyncTarget(stName, TimelineSourceFactory
|
st = new SyncTarget(stName, TimelineSourceFactory
|
||||||
.newInstance(timelineURI), timeline);
|
.newInstance(timelineURI, config), timeline);
|
||||||
syncTargets.add(st);
|
syncTargets.add(st);
|
||||||
|
|
||||||
// check for synch options
|
// check for synch options
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.jdblabs.timestamper.core;
|
package com.jdblabs.timestamper.core;
|
||||||
|
|
||||||
|
import com.jdbernard.util.SmartConfig;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
@@ -9,10 +10,10 @@ import java.net.URI;
|
|||||||
*/
|
*/
|
||||||
public class TimelineSourceFactory {
|
public class TimelineSourceFactory {
|
||||||
|
|
||||||
public static TimelineSource newInstance(URI uri) {
|
public static TimelineSource newInstance(URI uri, SmartConfig config) {
|
||||||
// File based
|
// File based
|
||||||
if ("file".equalsIgnoreCase(uri.getScheme())) {
|
if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||||
return new FileTimelineSource(uri);
|
return new FileTimelineSource(uri, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Twitter
|
// Twitter
|
||||||
@@ -23,6 +24,11 @@ public class TimelineSourceFactory {
|
|||||||
+ "sources are not yet supported.");
|
+ "sources are not yet supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Other HTTP, assume JDB Labs web app
|
||||||
|
else if ("http".equalsIgnoreCase(uri.getScheme())) {
|
||||||
|
return new JDBLabsWebTimelineSource(uri, config);
|
||||||
|
}
|
||||||
|
|
||||||
// SSH
|
// SSH
|
||||||
else if ("ssh".equalsIgnoreCase(uri.getScheme())) {
|
else if ("ssh".equalsIgnoreCase(uri.getScheme())) {
|
||||||
throw new UnsupportedOperationException("SSH based timeline sources"
|
throw new UnsupportedOperationException("SSH based timeline sources"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.jdblabs.timestamper.core;
|
package com.jdblabs.timestamper.core;
|
||||||
|
|
||||||
|
import com.jdbernard.util.SmartConfig;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user