Compare commits
No commits in common. "main" and "temp" have entirely different histories.
6
.gitignore
vendored
Normal file → Executable file
6
.gitignore
vendored
Normal file → Executable file
@ -1,9 +1,5 @@
|
||||
build/
|
||||
release
|
||||
dist/
|
||||
.gradle/
|
||||
staging/
|
||||
temp/
|
||||
release
|
||||
.sass-cache
|
||||
*.build.tar.gz
|
||||
*.sw?
|
||||
|
@ -1,20 +0,0 @@
|
||||
apply plugin: "groovy"
|
||||
apply plugin: "maven"
|
||||
|
||||
group = "com.jdblabs.timestamper"
|
||||
version = "1.2"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral() }
|
||||
|
||||
dependencies {
|
||||
compile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
compile 'ch.qos.logback:logback-core:1.1.2'
|
||||
compile 'com.martiansoftware:nailgun-server:0.9.1'
|
||||
compile 'org.slf4j:slf4j-api:1.7.10'
|
||||
compile 'com.jdbernard:jdb-util:3.4'
|
||||
compile 'com.jdblabs.timestamper:timestamper-lib:2.1'
|
||||
|
||||
compile files('lib/jansi-1.12-SNAPSHOT.jar')
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<project name="JDB Labs TimeStamper CLI" default="build">
|
||||
|
||||
<property environment="env"/>
|
||||
<property file="project.properties"/>
|
||||
<import file="jdb-build-1.10.xml"/>
|
||||
|
||||
<target name="package" depends="build">
|
||||
<property name="package.dir" value="${build.dir}/${name}-${version}"/>
|
||||
<mkdir dir="${package.dir}/lib"/>
|
||||
<copy file="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
tofile="${package.dir}/${name}-${version}.jar"/>
|
||||
<copy todir="${package.dir}">
|
||||
<filterset><filter token="VERSION" value="${version}"/></filterset>
|
||||
<fileset dir="${resources.dir}/bin"/>
|
||||
</copy>
|
||||
<copy todir="${package.dir}/lib">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar"/>
|
||||
<fileset dir="${resources.dir}/config"/>
|
||||
</copy>
|
||||
<zip basedir="${build.dir}" includes="${name}-${version}/"
|
||||
destfile="${build.dir}/${name}-${version}.zip"/>
|
||||
|
||||
</target>
|
||||
</project>
|
Binary file not shown.
@ -1,4 +0,0 @@
|
||||
curdir="`pwd`"
|
||||
cd ~/programs/timestamper-cli-@VERSION@
|
||||
java -cp "lib:lib/*:./*" com.jdblabs.timestamper.cli.TimeStamperCLI -d "$curdir" "$@"
|
||||
cd "$curdir"
|
@ -1 +0,0 @@
|
||||
rootProject.name = "timestamper-cli"
|
@ -1,320 +0,0 @@
|
||||
package com.jdblabs.timestamper.cli
|
||||
|
||||
import com.jdbernard.io.NonBlockingInputStreamReader
|
||||
import com.jdbernard.util.SmartConfig
|
||||
import com.jdbernard.util.LightOptionParser
|
||||
import com.jdblabs.timestamper.core.Timeline
|
||||
import com.jdblabs.timestamper.core.TimelineMarker
|
||||
import com.jdblabs.timestamper.core.TimelineProperties
|
||||
import com.martiansoftware.nailgun.NGContext
|
||||
|
||||
import org.fusesource.jansi.AnsiConsole
|
||||
import static org.fusesource.jansi.Ansi.*
|
||||
|
||||
import static org.fusesource.jansi.Ansi.*
|
||||
import static org.fusesource.jansi.Ansi.Color.*
|
||||
|
||||
public class TimeStamperCLI {
|
||||
|
||||
private static String EOL = System.getProperty("line.separator")
|
||||
|
||||
private static TimeStamperCLI nailgunInst
|
||||
|
||||
protected TimelineProperties timelineProperties
|
||||
protected Timeline timeline
|
||||
|
||||
public static final String VERSION = "1.2"
|
||||
|
||||
protected static def cli = [
|
||||
'h': [longName: 'help'],
|
||||
'v': [longName: 'version'],
|
||||
'd': [longName: 'working-directory', arguments: 1],
|
||||
't': [longName: 'timeline-config', arguments: 1],
|
||||
'tty': [longName: 'tty', arguments: 1]]
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
TimeStamperCLI inst = new TimeStamperCLI()
|
||||
doMain(inst, args, System.in, System.out, System.err); }
|
||||
|
||||
public static void nailMain(NGContext context) {
|
||||
if (nailgunInst == null)
|
||||
nailgunInst = new TimeStamperCLI()
|
||||
|
||||
doMain(nailgunInst, context.args, context.in, context.out, context.err) }
|
||||
|
||||
protected static void doMain(TimeStamperCLI inst, String[] args,
|
||||
def sin, def out, def err) {
|
||||
|
||||
//out = new PrintStream(AnsiConsole.wrapOutputStream(out))
|
||||
def opts = LightOptionParser.parseOptions(cli, args as List)
|
||||
|
||||
File workingDir = new File(opts.d[0] ?: '.')
|
||||
String ttyDevice = opts.tty ?: '/dev/tty'
|
||||
|
||||
if (opts.h) println USAGE
|
||||
|
||||
if (opts.v) {
|
||||
println "TimeStamperCLI v${VERSION}"
|
||||
println "By JDB Labs (https://www.jdb-labs.com)"
|
||||
return }
|
||||
|
||||
if (opts.t) {
|
||||
File propFile = new File(opts.t)
|
||||
if (!propFile.isAbsolute()) propFile = new File(workingDir, opts.t)
|
||||
inst.showTimeline(propFile, sin, out, err, ttyDevice) }
|
||||
else if (inst.timeline == null) {
|
||||
// Look for .timestamperrc user config file
|
||||
File cfgFile = new File(
|
||||
System.getProperty('user.home'), ".timestamperrc")
|
||||
if (!cfgFile.exists() || !cfgFile.isFile())
|
||||
err.println "Could not find the user configuration file: " +
|
||||
cfgFile.canonicalPath
|
||||
else {
|
||||
def cfg = new SmartConfig(cfgFile)
|
||||
inst.showTimeline(new File(cfg.lastUsed), sin, out, err, ttyDevice) } }
|
||||
else { inst.showTimeline(sin, out, err, ttyDevice) } }
|
||||
|
||||
public TimeStamperCLI() { }
|
||||
|
||||
public void showTimeline(File timelinePropertiesFile,
|
||||
def sin, def out, def err, String ttyDevice) {
|
||||
if (!timelinePropertiesFile.exists() ||
|
||||
!timelinePropertiesFile.isFile()) {
|
||||
|
||||
err.println "No such timeline property file: " +
|
||||
timelinePropertiesFile.canonicalPath
|
||||
|
||||
return }
|
||||
|
||||
this.timelineProperties = new TimelineProperties(timelinePropertiesFile)
|
||||
this.timeline = timelineProperties.timeline
|
||||
|
||||
showTimeline(sin, out, err, ttyDevice) }
|
||||
|
||||
public void showTimeline(final def sin, def out, def err, String ttyDevice) {
|
||||
|
||||
//out.println ""
|
||||
def currentMarker = timeline.getLastMarker(new Date())
|
||||
def reader = new NonBlockingInputStreamReader(sin)
|
||||
Thread readerThread = new Thread(reader)
|
||||
readerThread.start()
|
||||
|
||||
boolean running = true
|
||||
|
||||
def blockingReadLine = {
|
||||
String line = null;
|
||||
while (line == null && readerThread.isAlive()) {
|
||||
line = reader.readLine()
|
||||
|
||||
Thread.sleep(200) }
|
||||
|
||||
return line }
|
||||
|
||||
def readNotes = {
|
||||
out.println(ansi().fg(YELLOW).
|
||||
a("Notes (end with EOF or a blank line):").reset())
|
||||
|
||||
String notes = ""
|
||||
String line = null
|
||||
line = blockingReadLine()
|
||||
|
||||
while(line != "" && line != "EOF" && line != null) {
|
||||
notes += line + EOL
|
||||
line = blockingReadLine() }
|
||||
|
||||
return notes }
|
||||
|
||||
def printPrompt = {
|
||||
out.print(formatMarker(currentMarker) + EOL +
|
||||
ansi().fg(YELLOW).a("> ").reset())
|
||||
out.flush() }
|
||||
|
||||
String line = null
|
||||
|
||||
printPrompt()
|
||||
while (running && readerThread.isAlive()) {
|
||||
|
||||
// Handle user input
|
||||
line = reader.readLine()
|
||||
if (line != null) {
|
||||
out.flush();
|
||||
switch (line) {
|
||||
case ~/quit|exit|\u0004/:
|
||||
running = false;
|
||||
break
|
||||
|
||||
case ~/n|new/:
|
||||
|
||||
// Read mark
|
||||
out.println(ansi().fg(YELLOW).a("New timestamp mark:").reset())
|
||||
String mark = blockingReadLine()
|
||||
|
||||
// Read notes
|
||||
String notes = readNotes();
|
||||
|
||||
// Create marker
|
||||
currentMarker = new TimelineMarker(new Date(), mark, notes)
|
||||
timeline.addMarker(currentMarker)
|
||||
if (timelineProperties.persistOnUpdate)
|
||||
timelineProperties.save()
|
||||
break
|
||||
|
||||
case ~/h|help/:
|
||||
out.println(ansi().fg(RED).
|
||||
a("Not yet implemented.").reset());
|
||||
break
|
||||
|
||||
case ~/l|list|history/:
|
||||
out.println(ansi().fg(RED).
|
||||
a("Not yet implemented.").reset());
|
||||
break
|
||||
|
||||
case ~/s|save/:
|
||||
timelineProperties.save()
|
||||
break
|
||||
|
||||
case ~/reload/:
|
||||
timeline = timelineProperties.reloadTimeline()
|
||||
currentMarker = timeline.getLastMarker(new Date())
|
||||
break
|
||||
|
||||
case ~/e|ed|edit/:
|
||||
reader.pause()
|
||||
out.println(ansi().fg(YELLOW).
|
||||
a("Press ENTER to launch an editor for the current marker..."))
|
||||
out.flush()
|
||||
blockingReadLine()
|
||||
currentMarker = edit(currentMarker, ttyDevice)
|
||||
reader.resume()
|
||||
break
|
||||
|
||||
case ~/d|del|delete/:
|
||||
timeline.removeMarker(currentMarker)
|
||||
currentMarker = timeline.getLastMarker(new Date())
|
||||
break
|
||||
|
||||
default:
|
||||
String notes = readNotes()
|
||||
currentMarker = new TimelineMarker(new Date(), line, notes)
|
||||
timeline.addMarker(currentMarker)
|
||||
if (timelineProperties.persistOnUpdate)
|
||||
timelineProperties.save()
|
||||
break
|
||||
}
|
||||
printPrompt()
|
||||
} else {
|
||||
out.print(ansi().saveCursorPosition().cursorUpLine().eraseLine().toString() +
|
||||
formatMarker(currentMarker) +
|
||||
ansi().cursorDown(1).restorCursorPosition().toString())
|
||||
out.flush();
|
||||
|
||||
Thread.sleep(200)
|
||||
}
|
||||
}
|
||||
|
||||
this.timelineProperties.syncTargets.each { it.shutdown() }
|
||||
|
||||
if (readerThread.isAlive()) {
|
||||
readerThread.interrupt();
|
||||
readerThread.join(500);
|
||||
if (readerThread.isAlive()) readerThread.stop(); }
|
||||
|
||||
out.println ""
|
||||
}
|
||||
|
||||
protected TimelineMarker edit(TimelineMarker tm, String ttyDevice) {
|
||||
File temp = File.createTempFile('timestamp-mark-', '.txt')
|
||||
temp.withPrintWriter { fout ->
|
||||
fout.println("""\
|
||||
# Edit the time, mark, and notes below. Any lines starting with '#' will be
|
||||
# ignored. When done, save the file and close the editor.""")
|
||||
fout.println(Timeline.longFormat.format(tm.timestamp))
|
||||
fout.println(tm.mark)
|
||||
fout.println("""\
|
||||
# Everything from the line below to the end of the file will be considered
|
||||
# notes for this timeline mark.""")
|
||||
fout.println(tm.notes) }
|
||||
|
||||
['sh', '-c', "\$EDITOR ${temp.canonicalPath} <${ttyDevice} >${ttyDevice}"].execute().waitFor()
|
||||
|
||||
Thread.sleep(200)
|
||||
|
||||
String newTimeStr
|
||||
String newMark
|
||||
String newNotes = ""
|
||||
|
||||
temp.eachLine { line ->
|
||||
if (line =~ /^\s*#/) return
|
||||
|
||||
if (!newTimeStr) newTimeStr = line
|
||||
else if (!newMark) newMark = line
|
||||
else newNotes += line + EOL }
|
||||
|
||||
Date newTime
|
||||
try { newTime = Timeline.longFormat.parse(newTimeStr) }
|
||||
catch(Exception e) {
|
||||
out.println(ansi().fg(RED).a("Invalid timestamp format. The " +
|
||||
"previous timestamp value will be retained."))
|
||||
newTime = currentMark.timestamp }
|
||||
|
||||
timeline.removeMarker(tm)
|
||||
def newMarker = new TimelineMarker(newTime, newMark, newNotes)
|
||||
timeline.addMarker(newMarker)
|
||||
if (timelineProperties.persistOnUpdate)
|
||||
timelineProperties.save()
|
||||
|
||||
return newMarker
|
||||
|
||||
}
|
||||
|
||||
protected static String formatMarker(TimelineMarker tm) {
|
||||
return ansi().fgBright(CYAN).
|
||||
a(Timeline.shortFormat.format(tm.timestamp) + " ").fg(GREEN).
|
||||
a("(" + getDuration(tm) + ") ").fg(WHITE).a(tm.mark).toString() }
|
||||
|
||||
protected static String getDuration(TimelineMarker tm) {
|
||||
Date currentTime = new Date()
|
||||
long seconds = currentTime.time - tm.timestamp.time
|
||||
seconds /= 1000
|
||||
long minutes = seconds / 60
|
||||
seconds = seconds % 60
|
||||
long hours = minutes / 60
|
||||
minutes %= 60
|
||||
long days = hours / 24
|
||||
hours %= 24
|
||||
|
||||
StringBuilder sb = new StringBuilder()
|
||||
if (days > 0) sb.append(days + "day ")
|
||||
if (hours > 0) sb.append(hours + "hr ")
|
||||
if (minutes > 0) sb.append(minutes + "min ")
|
||||
sb.append(seconds + "sec")
|
||||
|
||||
return sb.toString() }
|
||||
|
||||
public static final String USAGE = """\
|
||||
TimeStamperCLI v${VERSION}
|
||||
By JDB Labs (https://www.jdb-labs.com)
|
||||
|
||||
Usage:
|
||||
ts <OPTIONS>
|
||||
where OPTIONS is one or more of:
|
||||
|
||||
-h, --help Print this usage information.
|
||||
|
||||
-v, --version Print version information.
|
||||
|
||||
-d, --working-directory Set the application's working direcotry (defaults to
|
||||
the current directory of the executing process).
|
||||
|
||||
-t, --timeline-config Set the timeline configuration file to use to access
|
||||
the timeline. By default, the value of the
|
||||
`lastUsed` property in the \$HOME/.timestamperrc
|
||||
file is used to find the timeline configuration file
|
||||
to use.
|
||||
|
||||
--tty Manually set the name of the TTY device to use. This
|
||||
defaults to `/dev/tty`.
|
||||
"""
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
apply plugin: "groovy"
|
||||
apply plugin: "maven"
|
||||
|
||||
group = "com.jdblabs.timestamper"
|
||||
version = "2.1"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral() }
|
||||
|
||||
dependencies {
|
||||
compile 'org.codehaus.groovy:groovy-all:2.3.6'
|
||||
compile 'org.pegdown:pegdown:1.4.2'
|
||||
compile 'commons-codec:commons-codec:1.10'
|
||||
compile 'commons-beanutils:commons-beanutils:1.9.2'
|
||||
compile 'org.codehaus.groovy.modules.http-builder:http-builder:0.6'
|
||||
compile 'org.apache.httpcomponents:httpclient:4.3.6'
|
||||
compile 'org.slf4j:slf4j-api:1.7.10'
|
||||
compile 'ch.qos.logback:logback-core:1.1.2'
|
||||
compile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
compile 'com.jdbernard:jdb-util:3.4'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes("Main-Class": "com.jdbernard.remindme.DailyAgenda")
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
Add unit tests.
|
||||
===============
|
||||
|
||||
----
|
||||
|
||||
========= ==========
|
||||
Created: 2011-06-28
|
||||
Resolved: YYYY-MM-DD
|
||||
========= ==========
|
@ -1 +0,0 @@
|
||||
rootProject.name = "timestamper-lib"
|
@ -1,21 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Bernard ({@literal jdbernard@gmail.com})
|
||||
*/
|
||||
public class AuthenticationException extends IOException {
|
||||
|
||||
public AuthenticationException() { super(); }
|
||||
|
||||
public AuthenticationException(String message) { super(message); }
|
||||
|
||||
public AuthenticationException(Throwable t) { super(t); }
|
||||
|
||||
public AuthenticationException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import com.jdbernard.util.SmartConfig;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Bernard ({@literal jdbernard@gmail.com})
|
||||
*/
|
||||
public class FileTimelineSource extends TimelineSource {
|
||||
|
||||
private File file;
|
||||
|
||||
public FileTimelineSource(URI uri, SmartConfig config) {
|
||||
super(uri);
|
||||
this.file = new File(uri);
|
||||
}
|
||||
|
||||
/** * {@inheritDoc } */
|
||||
public Timeline read() throws IOException {
|
||||
FileInputStream fin = new FileInputStream(file);
|
||||
Timeline t = StreamBasedTimelineSource.readFromStream(fin);
|
||||
fin.close();
|
||||
return t;
|
||||
}
|
||||
|
||||
public Timeline readWithComments(OutputStream commentStream)
|
||||
throws IOException {
|
||||
FileInputStream fin = new FileInputStream(file);
|
||||
Timeline t = StreamBasedTimelineSource.readFromStream(fin, commentStream);
|
||||
fin.close();
|
||||
return t;
|
||||
}
|
||||
|
||||
/** * {@inheritDoc } */
|
||||
public void persist(Timeline t) throws IOException {
|
||||
FileOutputStream fout = new FileOutputStream(file);
|
||||
StreamBasedTimelineSource.writeToStream(fout, t);
|
||||
fout.close();
|
||||
}
|
||||
|
||||
public boolean isAuthenticated() {
|
||||
File dir = file.getParentFile();
|
||||
return (dir.canRead() && dir.canWrite());
|
||||
}
|
||||
|
||||
public void authenticate() throws AuthenticationException {
|
||||
File dir = file.getParentFile();
|
||||
if (!dir.canRead())
|
||||
throw new AuthenticationException("This FileTimelineSource does not"
|
||||
+ " have read access to the parent directory for the "
|
||||
+ "given file (" + file.getAbsolutePath() + ".");
|
||||
|
||||
if (!dir.canWrite())
|
||||
throw new AuthenticationException("This FileTimelineSource does not"
|
||||
+ " have write access to the parent directory for the "
|
||||
+ "given file (" + file.getAbsolutePath() + ".");
|
||||
|
||||
}
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
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.*
|
||||
|
||||
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 serverEntryIds
|
||||
|
||||
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 + "/ts_api/")
|
||||
http = new HTTPBuilder(baseUri)
|
||||
|
||||
// set some default error handlers
|
||||
http.handler.'500' = { resp, json ->
|
||||
throw new IOException("The web timeline reported an internal " +
|
||||
"error: ${json.error ?: 'no details available'}") }
|
||||
|
||||
http.handler.failure = {resp, json ->
|
||||
throw new IOException("Unable to complete the operation: error " +
|
||||
"communicating to the web timeline.\n" +
|
||||
"${resp.statusLine}: ${json}") }
|
||||
|
||||
// init our map of known entries
|
||||
serverEntryIds = [:]
|
||||
}
|
||||
|
||||
public Timeline read() {
|
||||
def timelineJSON
|
||||
def entryListJSON
|
||||
def startDate, endDate
|
||||
Timeline timeline
|
||||
|
||||
// make sure we have a fresh session
|
||||
authenticate()
|
||||
|
||||
// load the timeline information
|
||||
http.request(GET, JSON) {
|
||||
uri.path = "timelines/${username}/${timelineId}"
|
||||
|
||||
response.'404' = { resp ->
|
||||
throw new IOException("No timeline '${timelineId}' for user " +
|
||||
"'${username}'") }
|
||||
|
||||
response.success = { resp, json -> timelineJSON = json }
|
||||
}
|
||||
|
||||
timeline = new Timeline()
|
||||
|
||||
// create start and end times to use as parameters to the web service
|
||||
startDate = Calendar.getInstance()
|
||||
startDate.timeInMillis = 0 // Beginning of the epoch
|
||||
startDate = isoDateFormat.format(startDate.time)
|
||||
|
||||
// Until now
|
||||
endDate = isoDateFormat.format(Calendar.getInstance().time)
|
||||
|
||||
// load the timeline entries
|
||||
entryListJSON = http.get(
|
||||
path: "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,
|
||||
UUID.fromString(entry.uuid));
|
||||
|
||||
// add it to the timeline and our map of hashes
|
||||
timeline.addMarker(marker)
|
||||
serverEntryIds[marker.uuid] = 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.
|
||||
|
||||
// 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 ->
|
||||
// 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 not present on the server, add to newEntries.
|
||||
else { newEntries << marker }
|
||||
}
|
||||
|
||||
// Delete all entries that used to be present but are not any longer
|
||||
deletedEntries.each { uuid, entryId ->
|
||||
http.request(DELETE) {
|
||||
uri.path = "entries/${username}/${timelineId}/${entryId}"
|
||||
|
||||
response.'404' = { resp -> serverEntryIds.remove(uuid) }
|
||||
response.success = { resp -> serverEntryIds.remove(uuid) }
|
||||
}
|
||||
}
|
||||
|
||||
// add all new entries
|
||||
newEntries.each { entry ->
|
||||
|
||||
def entryBody = [
|
||||
mark: entry.mark,
|
||||
notes: entry.notes,
|
||||
uuid: entry.uuid.toString(),
|
||||
timestamp: isoDateFormat.format(entry.timestamp)
|
||||
]
|
||||
|
||||
http.request(POST, JSON) {
|
||||
uri.path = "entries/${username}/${timelineId}"
|
||||
requestContentType = JSON
|
||||
body = entryBody
|
||||
|
||||
response.success = { resp, json ->
|
||||
serverEntryIds.put(entry.uuid, json?.id ?: 0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAuthenticated() { return false; }
|
||||
|
||||
public void authenticate() throws AuthenticationException {
|
||||
http.request(POST, JSON) { req ->
|
||||
uri.path = '/ts_api/login'
|
||||
requestContentType = JSON
|
||||
body = [ username: this.username,
|
||||
password: this.password ]
|
||||
|
||||
response.'401' = { resp ->
|
||||
throw new AuthenticationException(
|
||||
"Unable to connect to ${baseUri}: " +
|
||||
"Invalid username/password combination.") }
|
||||
}
|
||||
}
|
||||
|
||||
protected int fullHash(TimelineMarker tm) {
|
||||
return 61 * tm.hashCode() + (tm.mark != null ? tm.mark.hashCode() : 0);
|
||||
}
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Bernard ({@literal jdbernard@gmail.com})
|
||||
*/
|
||||
public class StreamBasedTimelineSource extends TimelineSource {
|
||||
|
||||
private static final int lineWrap = 78;
|
||||
|
||||
private static enum ReadingState {
|
||||
NewMarker,
|
||||
StartMark,
|
||||
ReadMark,
|
||||
StartNotes,
|
||||
ReadNotes,
|
||||
EndMarker };
|
||||
|
||||
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
private ByteArrayOutputStream comments;
|
||||
|
||||
public StreamBasedTimelineSource(InputStream inStream,
|
||||
OutputStream outStream) {
|
||||
super(null);
|
||||
this.in = inStream;
|
||||
this.out = outStream;
|
||||
this.comments = new ByteArrayOutputStream(); }
|
||||
|
||||
/** {@inheritDoc } */
|
||||
public Timeline read() throws IOException {
|
||||
return readFromStream(in, comments); }
|
||||
|
||||
/** {@inheritDoc } */
|
||||
public void persist(Timeline t) throws IOException {
|
||||
writeToStream(out, t); }
|
||||
|
||||
public void authenticate() throws AuthenticationException { }
|
||||
public boolean isAuthenticated() { return true; }
|
||||
|
||||
/**
|
||||
* Allows a user to extract the comments from the last parsed file.
|
||||
* @return a <code>byte[]</code> representing the portions of the file
|
||||
* which were comments.
|
||||
*/
|
||||
public byte[] getCommentBytes() { return comments.toByteArray(); }
|
||||
|
||||
/**
|
||||
* Write the a representation of the timeline to a stream. This method
|
||||
* flushes the stream after it finishes writing but does not close the
|
||||
* stream.
|
||||
* @param stream An open stream to write the timeline representation to.
|
||||
* @param timeline The timeline to write.
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static void writeToStream(OutputStream stream, Timeline timeline)
|
||||
throws IOException {
|
||||
Writer out = new OutputStreamWriter(stream);
|
||||
for (TimelineMarker tm : timeline) {
|
||||
|
||||
// write timestamp and UUID
|
||||
out.write(Timeline.longFormat.format(tm.getTimestamp()) + "," +
|
||||
tm.getUuid().toString() + "\n");
|
||||
|
||||
// write mark
|
||||
String mark = tm.getMark().replace('\n', '\u0000');
|
||||
if (mark.length() < lineWrap) out.write(mark + "\n");
|
||||
else {
|
||||
// wrap lines if neccessary
|
||||
int i;
|
||||
for (i = 0; (i + lineWrap) < mark.length(); i+=lineWrap)
|
||||
out.write(mark.substring(i, i+lineWrap) + "\\\n");
|
||||
if (i < mark.length())
|
||||
out.write(mark.substring(i, mark.length()) + "\n"); }
|
||||
|
||||
// write notes
|
||||
String notes = tm.getNotes().replace('\n', '\u0000');
|
||||
if (notes.length() < lineWrap) out.write(notes + "\n");
|
||||
else {
|
||||
// wrap lines if neccessary
|
||||
int i;
|
||||
for (i = 0; (i + lineWrap) < notes.length(); i+=lineWrap)
|
||||
out.write(notes.substring(i, i+lineWrap) + "\\\n");
|
||||
if (i < notes.length())
|
||||
out.write(notes.substring(i, notes.length()) + "\n"); }
|
||||
out.write("\n"); }
|
||||
out.flush(); }
|
||||
|
||||
|
||||
/**
|
||||
* Create a <b>Timeline</b> instance from a given stream.
|
||||
* @param stream The stream to read from.
|
||||
* @return A new <b>Timeline</b> instance specified by the input stream.
|
||||
* @throws java.io.IOException
|
||||
* @throws java.io.FileNotFoundException
|
||||
*/
|
||||
public static Timeline readFromStream(InputStream stream)
|
||||
throws IOException {
|
||||
return readFromStream(stream, null); }
|
||||
|
||||
/**
|
||||
* Create a <b>Timeline</b> instance from a given stream.
|
||||
* @param stream The stream to read from.
|
||||
* @param commentStream A stream to write comments found in the file. This
|
||||
* parameter may be <b>null</b>, in which case comments are ignored.
|
||||
* @return A new <b>Timeline</b> instance specified by the input stream.
|
||||
* @throws java.io.IOException
|
||||
* @throws java.io.FileNotFoundException
|
||||
*/
|
||||
public static Timeline readFromStream(InputStream stream,
|
||||
OutputStream commentStream) throws IOException {
|
||||
Scanner in = new Scanner(stream);
|
||||
Timeline timeline = new Timeline();
|
||||
PrintWriter commentsWriter = commentStream == null ?
|
||||
null : new PrintWriter(commentStream);
|
||||
|
||||
ReadingState readingState = ReadingState.NewMarker;
|
||||
Date date = null;
|
||||
UUID uuid = null;
|
||||
StringBuilder mark = null;
|
||||
StringBuilder notes = null;
|
||||
String line;
|
||||
int lineNumber = 0;
|
||||
|
||||
while (in.hasNextLine()) {
|
||||
line = in.nextLine();
|
||||
lineNumber++;
|
||||
|
||||
// line is a comment
|
||||
if (line.startsWith("#")) {
|
||||
if (commentsWriter != null) commentsWriter.println(line);
|
||||
// don't parse this line as part of the timeline
|
||||
continue; }
|
||||
|
||||
switch (readingState) {
|
||||
|
||||
case NewMarker:
|
||||
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;
|
||||
|
||||
case StartMark:
|
||||
mark = new StringBuilder();
|
||||
// fall through to ReadMark
|
||||
case ReadMark:
|
||||
if (line.endsWith("\\")) {
|
||||
readingState = ReadingState.ReadMark;
|
||||
line = line.substring(0, line.length() - 1); }
|
||||
else readingState = ReadingState.StartNotes;
|
||||
mark.append(line);
|
||||
break;
|
||||
|
||||
case StartNotes:
|
||||
notes = new StringBuilder();
|
||||
// fall through to ReadNotes
|
||||
case ReadNotes:
|
||||
if (line.endsWith("\\")) {
|
||||
readingState = ReadingState.ReadNotes;
|
||||
line = line.substring(0, line.length() - 1); }
|
||||
else readingState = ReadingState.EndMarker;
|
||||
notes.append(line);
|
||||
break;
|
||||
|
||||
case EndMarker:
|
||||
String sMark = mark.toString().replace('\u0000', '\n');
|
||||
String sNotes = notes.toString().replace('\u0000', '\n');
|
||||
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,143 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* A remote target synchronized against the local timeline.
|
||||
* @author Jonathan Bernard (jdbernard@gmail.com)
|
||||
*/
|
||||
public class SyncTarget {
|
||||
|
||||
protected final TimelineSource source;
|
||||
protected final Timeline localTimeline;
|
||||
protected final String name;
|
||||
|
||||
protected Timer syncTimer;
|
||||
protected SyncTask syncTask;
|
||||
|
||||
protected long syncInterval= 30 * 60 * 1000; // 30 minutes converted to ms
|
||||
protected boolean pushEnabled = true;
|
||||
protected boolean pullEnabled = true;
|
||||
protected boolean syncOnExit = true;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected class SyncTask extends TimerTask {
|
||||
@Override public void run() {
|
||||
synchronized(this) {
|
||||
try { SyncTarget.this.sync(); }
|
||||
catch (IOException ioe) { /* TODO */ }
|
||||
}}}
|
||||
|
||||
protected SyncTarget(String name, TimelineSource source,
|
||||
Timeline localTimeline) {
|
||||
this.name = name;
|
||||
this.source = source;
|
||||
this.localTimeline = localTimeline;
|
||||
|
||||
syncTimer = new Timer(source.toString() + " sync-timer");
|
||||
syncTask = new SyncTask();
|
||||
syncTimer.schedule(syncTask, syncInterval, syncInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the local timeline with the remote timeline represented by this
|
||||
* SyncTarget object.
|
||||
* @return <b>true</b> if the two timelines were out of sync and have
|
||||
* been put into synch, <b>false</b> if the timelines were already in
|
||||
* sync and no action was requried.
|
||||
* @throws IOException if there is an error communicating with the remote
|
||||
* timeline. This includes AuthenticationException.
|
||||
*/
|
||||
protected boolean sync() throws IOException {
|
||||
assert (pullEnabled || pushEnabled);
|
||||
|
||||
Timeline remoteTimeline;
|
||||
boolean syncPerformed = false;
|
||||
|
||||
// make sure we're authenticated to whatever source we're using
|
||||
if (!SyncTarget.this.source.isAuthenticated()) {
|
||||
SyncTarget.this.source.authenticate();
|
||||
}
|
||||
|
||||
// try to copy the remote timeline locally
|
||||
remoteTimeline = SyncTarget.this.source.read();
|
||||
|
||||
// if we are pulling markers from the remote line
|
||||
if (SyncTarget.this.pullEnabled) {
|
||||
// get all markers in the remote timeline not in the local one
|
||||
Collection<TimelineMarker> diffFromRemote =
|
||||
remoteTimeline.difference(localTimeline);
|
||||
|
||||
if (diffFromRemote.size() != 0) {
|
||||
// add all markers in the remote tline to the local one
|
||||
localTimeline.addAll(diffFromRemote);
|
||||
syncPerformed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if we are pushing markers to the remote timeline
|
||||
if (SyncTarget.this.pushEnabled) {
|
||||
// get all markers in the local timeline but not in the remote
|
||||
Collection<TimelineMarker> diffFromLocal =
|
||||
localTimeline.difference(remoteTimeline);
|
||||
|
||||
if (diffFromLocal.size() != 0) {
|
||||
// add the difference to the remote timeline
|
||||
remoteTimeline.addAll(diffFromLocal);
|
||||
syncPerformed = true;
|
||||
}
|
||||
|
||||
// try to persist the updated remote timeline
|
||||
SyncTarget.this.source.persist(remoteTimeline);
|
||||
}
|
||||
|
||||
return syncPerformed;
|
||||
}
|
||||
|
||||
public void shutdown() throws IOException {
|
||||
// TODO: move this onto the timer thread?
|
||||
if (syncOnExit) sync();
|
||||
|
||||
syncTimer.cancel();
|
||||
syncTimer.purge();
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
|
||||
public TimelineSource getSource() { return source; }
|
||||
|
||||
public synchronized void setSyncInterval(long syncInterval) {
|
||||
this.syncInterval= syncInterval;
|
||||
|
||||
syncTask.cancel();
|
||||
syncTask = new SyncTask();
|
||||
|
||||
syncTimer.purge();
|
||||
syncTimer.schedule(syncTask, syncInterval, syncInterval);
|
||||
}
|
||||
|
||||
public long getSyncInterval() { return syncInterval; }
|
||||
|
||||
public synchronized void setPushEnabled(boolean pushEnabled) {
|
||||
this.pullEnabled = pushEnabled;
|
||||
}
|
||||
|
||||
public boolean getPushEnabled() { return pushEnabled; }
|
||||
|
||||
public synchronized void setPullEnabled(boolean pullEnabled) {
|
||||
this.pullEnabled = pullEnabled;
|
||||
}
|
||||
|
||||
public boolean getPullEnabled() { return pullEnabled; }
|
||||
|
||||
public synchronized void setSyncOnExit(boolean syncOnExit) {
|
||||
this.syncOnExit = syncOnExit;
|
||||
}
|
||||
|
||||
public boolean getSyncOnExit() { return syncOnExit; }
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* A Timeline object represents a series of markers at specific points in time.
|
||||
* It represents on logical timeline. The markers have a name or symbol (the
|
||||
* 'mark') and notes associated with that mark.
|
||||
* @author Jonathan Bernard {@literal <jdbernard@gmail.com>}
|
||||
* @see com.jdbernard.timestamper.core.TimelineSource
|
||||
*/
|
||||
public class Timeline implements Iterable<TimelineMarker> {
|
||||
|
||||
public static SimpleDateFormat shortFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
public static SimpleDateFormat longFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
|
||||
protected TreeSet<TimelineMarker> timelineList;
|
||||
|
||||
/**
|
||||
* Create a new, empty Timeline.
|
||||
*/
|
||||
public Timeline() {
|
||||
timelineList = new TreeSet<TimelineMarker>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a marker to the timeline.
|
||||
* @param tm The TimelineMarker to add.
|
||||
* @return <code>true</code> if this Timeline was modified.
|
||||
*/
|
||||
public boolean addMarker(TimelineMarker tm) { return timelineList.add(tm); }
|
||||
|
||||
/**
|
||||
* Add a marker to the timeline.
|
||||
* @param timestamp The date and time of the marker.
|
||||
* @param name The name of the marker (activity, project, etc)
|
||||
* @param notes Additional notes about this marker.
|
||||
* @return <code>true</code> if this Timeline was modified.
|
||||
*/
|
||||
public boolean addMarker(Date timestamp, String name, String notes) {
|
||||
return addMarker(new TimelineMarker(timestamp, name, notes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last marker placed before or on the given timestamp. If you
|
||||
* think of the markers as demarcating time, then this is effectivly,
|
||||
* "get the current task."
|
||||
* @param timestamp The cut-off point for the query.
|
||||
* @return The latest TimelineMarker placed on or before the given
|
||||
* timestamp.
|
||||
*/
|
||||
public TimelineMarker getLastMarker(Date timestamp) {
|
||||
TimelineMarker lastMarker = null;
|
||||
for (TimelineMarker tm : timelineList) {
|
||||
if (tm.getTimestamp().after(timestamp))
|
||||
break;
|
||||
lastMarker = tm;
|
||||
}
|
||||
|
||||
return lastMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a TimelineMarker from this Timelnie.
|
||||
* @param marker The marker to remove.
|
||||
* @return <code>true</code> if this Timeline was changed.
|
||||
*/
|
||||
public boolean removeMarker(TimelineMarker marker) {
|
||||
return timelineList.remove(marker);
|
||||
}
|
||||
|
||||
public Iterator<TimelineMarker> iterator() {
|
||||
return timelineList.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the difference of the this timeline relative to another timeline.
|
||||
* More specifically, return the set of all
|
||||
* {@link jdbernard.timestamper.core.TimelineMarker}s that are present in
|
||||
* the <b>this</b> timeline but not present in the given timeline.
|
||||
* timeline.
|
||||
* @param t
|
||||
* @return A collection representing the TimelineMarkers present in
|
||||
* <b>this</b> timeline but not in the given timeline.
|
||||
*/
|
||||
public Collection<TimelineMarker> difference(Timeline t) {
|
||||
TreeSet<TimelineMarker> difference = new TreeSet<TimelineMarker>();
|
||||
|
||||
for (TimelineMarker tm : timelineList) {
|
||||
if (!t.timelineList.contains(tm))
|
||||
difference.add(tm);
|
||||
}
|
||||
|
||||
return difference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all TimelineMarkers from <code>t</code> to <code>this</code>
|
||||
* Timeline, excluding markers already present in <code>this</code>.
|
||||
* @param t
|
||||
* @return <code>true</code> if this Timeline was modified.
|
||||
*/
|
||||
public boolean addAll(Timeline t) {
|
||||
boolean modified = false;
|
||||
for (TimelineMarker tm : t) {
|
||||
modified = addMarker(tm) || modified; }
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all TimelineMarkers from <code>c</code> to <code>this</code>
|
||||
* Timeline, excluding markers already present in <code>this</code>.
|
||||
* @param c A Collection of TimelineMarkers
|
||||
* @return <code>true</code> if this TImeline was modified.
|
||||
*/
|
||||
public boolean addAll(Collection<TimelineMarker> c) {
|
||||
return timelineList.addAll(c);
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Jonathan Bernard {@literal <jdbernard@gmail.com>}
|
||||
* This represents a marker on the timeline.
|
||||
* The date of the marker and the mark cannot be changed once assigned.
|
||||
*/
|
||||
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.");
|
||||
|
||||
// We truncate milliseconds.
|
||||
long seconds = 1000l * (timestamp.getTime() / 1000l);
|
||||
this.timestamp = new Date(seconds);
|
||||
this.mark = mark;
|
||||
this.notes = notes;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public Date getTimestamp() { return timestamp; }
|
||||
|
||||
public String getMark() { return mark; }
|
||||
|
||||
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);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof TimelineMarker)) return false;
|
||||
|
||||
TimelineMarker that = (TimelineMarker) o;
|
||||
return this.uuid.equals(that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 61 * hash + (this.timestamp != null ? this.timestamp.hashCode() : 0);
|
||||
hash = 61 * hash + (this.mark != null ? this.mark.hashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,208 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.jdbernard.util.SmartConfig;
|
||||
|
||||
/**
|
||||
* Represents a Timeline configuration. A configuration has one primary, or
|
||||
* local, Timeline with an associated TimelineSource, and a list of remote
|
||||
* Timelines synched to the local Timeline.
|
||||
* <br>
|
||||
* <table>
|
||||
* <tr><th>Property</th><th>Description</th></tr>
|
||||
* <tr><td><code>timeline.uri</code></td><td>The URI of the primary (local)
|
||||
* timeline</td></tr>
|
||||
* <tr><td><code>timeline.persistOnUpdate?</code></td><td><code>true</code> to
|
||||
* persist the local timeline when a new event is entered,
|
||||
* <code>false</code> to disable.</td></tr>
|
||||
* <tr><td><code>remote.timeline.</code><i>name</i><code>.uri</code></td>
|
||||
* <td>The URI for the <i>name</i> remote timeline.</td></tr>
|
||||
* <tr><td><code>remote.timeline.</code><i>name</i><code>.push?</code></td>
|
||||
* <td><code>true</code> to enable pushing updates to the <i>name</i>
|
||||
* remote timeline, <code>false</code> to disable.</td></tr>
|
||||
* <tr><td><code>remote.timeline.</code><i>name</i><code>.pull?</code></td>
|
||||
* <td><code>true</code> to enable pulling updates from the <i>name</i>
|
||||
* remote timeline, <code>false</code> to disable.</td></tr>
|
||||
* <tr><td><code>remote.timeline.</code><i>name</i><code>.syncOnExit?</code>
|
||||
* </td><td><code>true</code> to force sync the <i>name</i> remote
|
||||
* timeline on exit.</td></tr>
|
||||
* <tr><td><code>remote.timeline.</code><i>name</i>
|
||||
* <code>.updateInterval</code></td><td>The time in milliseconds between
|
||||
* synching the <i>name</i> remote timeline.</td></tr></table>
|
||||
* @author Jonathan Bernard ({@literal jdbernard@gmail.com})
|
||||
* @see com.jdbernard.timestamper.core.Timeline
|
||||
* @see com.jdbernard.timestamper.core.TimelineSource
|
||||
* @see com.jdbernard.timestamper.core.SyncTarget
|
||||
*/
|
||||
public class TimelineProperties {
|
||||
|
||||
public static final String LOCAL_TIMELINE_URI = "timeline.uri";
|
||||
public static final String LOCAL_TIMELINE_PERSIST_ON_UPDATE = "timeline.persistOnUpdate?";
|
||||
public static final String REMOTE_TIMELINE_BASE = "remote.timeline.";
|
||||
|
||||
private static final Pattern remoteTimelinePropPattern =
|
||||
Pattern.compile("\\Q" + REMOTE_TIMELINE_BASE + "\\E([^\\s\\.=]+?)[\\.=].*");
|
||||
|
||||
private SmartConfig config;
|
||||
private Timeline timeline;
|
||||
private TimelineSource timelineSource;
|
||||
private LinkedList<SyncTarget> syncTargets = new LinkedList<SyncTarget>();
|
||||
private boolean persistOnUpdate;
|
||||
|
||||
/**
|
||||
* Create new TimelineProperties, using default values. This will create
|
||||
* a new configuration using a FileTimelineSource pointed at
|
||||
* <code>'timeline.default.txt'</code> in the current directory and no
|
||||
* remote Timelines. It will save this configuration to
|
||||
* <code>'timeline.default.properties'</code> in the current directory.
|
||||
*/
|
||||
public TimelineProperties() {
|
||||
File propertyFile = new File("timeline.default.properties");
|
||||
config = new SmartConfig(propertyFile);
|
||||
|
||||
File timelineFile = new File("timeline.default.txt");
|
||||
URI timelineURI = timelineFile.toURI();
|
||||
|
||||
timeline = new Timeline();
|
||||
timelineSource = TimelineSourceFactory.newInstance(timelineURI, config);
|
||||
persistOnUpdate = true;
|
||||
try { timelineSource.persist(timeline); }
|
||||
catch (IOException ioe) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
config.setProperty(LOCAL_TIMELINE_URI, timelineURI);
|
||||
config.setProperty(LOCAL_TIMELINE_PERSIST_ON_UPDATE, persistOnUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load TimelineProperties from a config file.
|
||||
* @param propFile
|
||||
*/
|
||||
public TimelineProperties(File propFile) throws IOException {
|
||||
String strURI;
|
||||
URI timelineURI;
|
||||
|
||||
config = new SmartConfig(propFile);
|
||||
|
||||
// load persist on update information
|
||||
persistOnUpdate = (Boolean) config.getProperty(
|
||||
LOCAL_TIMELINE_PERSIST_ON_UPDATE, true);
|
||||
|
||||
// get the URI for the primary timeline
|
||||
strURI = (String) config.getProperty(LOCAL_TIMELINE_URI, "");
|
||||
|
||||
// no primary timeline, default to file-based timeline
|
||||
if ("".equals(strURI)) {
|
||||
File defaultTimelineFile = new File("timeline.default.txt");
|
||||
try {
|
||||
if (!defaultTimelineFile.exists())
|
||||
defaultTimelineFile.createNewFile();
|
||||
} catch (IOException ioe) {
|
||||
// TODO
|
||||
}
|
||||
timelineURI = defaultTimelineFile.toURI();
|
||||
} else { // we do have a URI
|
||||
try { timelineURI = new URI(strURI); }
|
||||
catch (URISyntaxException urise) {
|
||||
throw new IOException("Unable to load the timeline: the timeline "
|
||||
+ "URI is invalid.", urise);
|
||||
}
|
||||
}
|
||||
|
||||
// create our timeline source and read the timeline
|
||||
timelineSource = TimelineSourceFactory.newInstance(timelineURI, config);
|
||||
timeline = timelineSource.read();
|
||||
|
||||
// search keys for remote timeline entries
|
||||
for (Object keyObj : config.keySet()) {
|
||||
if (!(keyObj instanceof String)) continue;
|
||||
|
||||
String key = (String) keyObj;
|
||||
String stName;
|
||||
String remoteBase;
|
||||
SyncTarget st;
|
||||
|
||||
Matcher m = remoteTimelinePropPattern.matcher(key);
|
||||
if (!m.matches()) continue;
|
||||
|
||||
stName = m.group(1);
|
||||
|
||||
// skip if we have already setup this remote sync
|
||||
boolean stExists = false;
|
||||
for (SyncTarget target : syncTargets)
|
||||
if (target.getName().equals(stName))
|
||||
stExists = true;
|
||||
if (stExists) continue;
|
||||
|
||||
remoteBase = REMOTE_TIMELINE_BASE + stName;
|
||||
|
||||
strURI = (String) config.getProperty(remoteBase + ".uri", "");
|
||||
try { timelineURI = new URI(strURI); }
|
||||
catch (URISyntaxException urise) { /* TODO */ }
|
||||
|
||||
// add a new SyncTarget to the list
|
||||
st = new SyncTarget(stName, TimelineSourceFactory
|
||||
.newInstance(timelineURI, config), timeline);
|
||||
syncTargets.add(st);
|
||||
|
||||
// check for synch options
|
||||
st.setPullEnabled((Boolean)config.getProperty(
|
||||
remoteBase + ".pull?", true));
|
||||
st.setPushEnabled((Boolean)config.getProperty(
|
||||
remoteBase + ".push?", true));
|
||||
st.setSyncOnExit((Boolean)config.getProperty(
|
||||
remoteBase + ".syncOnExit?", true));
|
||||
st.setSyncInterval((Long)config.getProperty(
|
||||
remoteBase + ".updateInterval", 1800000l)); // thirty minutes
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
timelineSource.persist(timeline);
|
||||
|
||||
config.setProperty(LOCAL_TIMELINE_PERSIST_ON_UPDATE, persistOnUpdate);
|
||||
config.setProperty(LOCAL_TIMELINE_URI, timelineSource.getURI());
|
||||
|
||||
for (SyncTarget st : syncTargets) {
|
||||
String remoteBase = REMOTE_TIMELINE_BASE + st.getName();
|
||||
config.setProperty(remoteBase + ".uri", st.getSource().getURI());
|
||||
config.setProperty(remoteBase + ".pull?", st.getPullEnabled());
|
||||
config.setProperty(remoteBase + ".push?", st.getPushEnabled());
|
||||
config.setProperty(remoteBase + ".syncOnExit?", st.getSyncOnExit());
|
||||
config.setProperty(remoteBase + ".updateInterval",
|
||||
st.getSyncInterval());
|
||||
}
|
||||
}
|
||||
|
||||
public Timeline reloadTimeline() throws IOException {
|
||||
timeline = timelineSource.read();
|
||||
return timeline;
|
||||
}
|
||||
|
||||
public Timeline getTimeline() { return timeline; }
|
||||
|
||||
public void setTimelineSource(TimelineSource newSource) {
|
||||
this.timelineSource = newSource;
|
||||
}
|
||||
|
||||
public TimelineSource getTimelineSource() { return timelineSource; }
|
||||
|
||||
public Collection<SyncTarget> getSyncTargets() { return syncTargets; }
|
||||
|
||||
public boolean getPersistOnUpdate() { return persistOnUpdate; }
|
||||
|
||||
public void setPersistOnUpdate(boolean persistOnUpdate) {
|
||||
this.persistOnUpdate = persistOnUpdate;
|
||||
if (persistOnUpdate) try { save(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A means of loading and persisting a Timeline.
|
||||
* @author Jonathan Bernard (jdbernard@gmail.com)
|
||||
*/
|
||||
public abstract class TimelineSource {
|
||||
|
||||
protected final URI uri;
|
||||
|
||||
public TimelineSource(URI uri) { this.uri = uri; }
|
||||
|
||||
/**
|
||||
* Read the Timeline from the source.
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract Timeline read() throws IOException;
|
||||
|
||||
/**
|
||||
* Persist a give timeline to this source.
|
||||
* @param t
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract void persist(Timeline t) throws IOException;
|
||||
|
||||
/**
|
||||
* Is this source authenticated and ready for IO.
|
||||
* @return <code>true</code> if the source is authenticated (or if no
|
||||
* authentication is required).
|
||||
*/
|
||||
public abstract boolean isAuthenticated();
|
||||
|
||||
/**
|
||||
* Authenticate the client to this source.
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public abstract void authenticate() throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* Get the URI representing this source.
|
||||
* @return The {@link java.net.URI} representing this source.
|
||||
*/
|
||||
public URI getURI() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import com.jdbernard.util.SmartConfig;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Bernard ({@literal jdbernard@gmail.com})
|
||||
*/
|
||||
public class TimelineSourceFactory {
|
||||
|
||||
public static TimelineSource newInstance(URI uri, SmartConfig config) {
|
||||
// File based
|
||||
if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return new FileTimelineSource(uri, config);
|
||||
}
|
||||
|
||||
// Twitter
|
||||
else if ("http".equalsIgnoreCase(uri.getScheme()) &&
|
||||
("twitter.com".equalsIgnoreCase(uri.getHost())
|
||||
|| "www.twitter.com".equalsIgnoreCase(uri.getHost()))) {
|
||||
throw new UnsupportedOperationException("Twitter based timeline "
|
||||
+ "sources are not yet supported.");
|
||||
}
|
||||
|
||||
// Other HTTP, assume JDB Labs web app
|
||||
else if ("http".equalsIgnoreCase(uri.getScheme())) {
|
||||
return new JDBLabsWebTimelineSource(uri, config);
|
||||
}
|
||||
|
||||
// SSH
|
||||
else if ("ssh".equalsIgnoreCase(uri.getScheme())) {
|
||||
throw new UnsupportedOperationException("SSH based timeline sources"
|
||||
+ " are not yet supported.");
|
||||
}
|
||||
|
||||
// unknown
|
||||
else {
|
||||
throw new UnsupportedOperationException("Timeline sources for the"
|
||||
+ " " + uri.getScheme() + " are not currently supported.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package com.jdblabs.timestamper.core;
|
||||
|
||||
import com.jdbernard.util.SmartConfig;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Bernard ({@literal jdbernard@gmail.com})
|
||||
*/
|
||||
public class TwitterTimelineSource extends TimelineSource {
|
||||
|
||||
private String username;
|
||||
private char[] password;
|
||||
|
||||
public TwitterTimelineSource(URI uri) {
|
||||
super(uri);
|
||||
}
|
||||
|
||||
public Timeline read() throws IOException {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void persist(Timeline t) throws IOException {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public boolean isAuthenticated() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void authenticate() throws AuthenticationException {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
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,19 +0,0 @@
|
||||
<project name="Time Analyzer" default="compile">
|
||||
|
||||
<property file="project.properties"/>
|
||||
<import file="jdb-build-1.9.xml"/>
|
||||
|
||||
<target name="init"/>
|
||||
|
||||
<target name="build-shell" depends="build">
|
||||
<mkdir dir="${build.dir}/shell"/>
|
||||
<copy todir="${build.dir}/shell">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar">
|
||||
<include name="*.jar"/>
|
||||
<exclude name="groovy-*.jar"/>
|
||||
</fileset>
|
||||
<fileset dir="${build.dir}/main/classes"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
</project>
|
@ -1,248 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<project name="Jonathan Bernard Build Common"
|
||||
xmlns:ivy="antlib:org.apache.ivy.ant">
|
||||
|
||||
<property environment="env"/>
|
||||
|
||||
<!--======== INIT TARGETS ========-->
|
||||
<target name="-init" depends="-common-init,init"/>
|
||||
|
||||
<target name="-common-init">
|
||||
<!-- Set default values for some key properties. Since properties are
|
||||
write once, any value set before this point takes precedence. -->
|
||||
|
||||
<property name="versioning.file" value="project.properties"/>
|
||||
|
||||
<property name="src.dir" value="${basedir}/src"/>
|
||||
<property name="build.dir" value="${basedir}/build"/>
|
||||
<property name="lib.dir" value="${basedir}/lib"/>
|
||||
<property name="resources.dir" value="${basedir}/resources"/>
|
||||
<property name="splash.image" value="splash.png"/>
|
||||
|
||||
<!--======== PATHS ========-->
|
||||
<path id="groovy.classpath">
|
||||
<fileset dir="${env.GROOVY_HOME}/lib">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<path id="groovy.embeddable">
|
||||
<fileset dir="${env.GROOVY_HOME}/embeddable">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<path id="compile-libs">
|
||||
<fileset dir="${build.dir}/lib/compile/jar">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<path id="runtime-libs">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="-init-groovy">
|
||||
<taskdef name="groovyc" classpathref="groovy.classpath"
|
||||
classname="org.codehaus.groovy.ant.Groovyc"/>
|
||||
|
||||
<taskdef name="groovy" classpathref="groovy.classpath"
|
||||
classname="org.codehaus.groovy.ant.Groovy"/>
|
||||
</target>
|
||||
|
||||
<target name="init"/>
|
||||
|
||||
<target name="clean" depends="-init">
|
||||
<delete dir="${build.dir}"/>
|
||||
</target>
|
||||
|
||||
<!--======== LIBRARY TARGETS ========-->
|
||||
<target name="-lib" depends="-lib-local,-lib-ivy,lib"/>
|
||||
|
||||
<target name="lib"/>
|
||||
|
||||
<target name="-init-ivy">
|
||||
<ivy:settings id="ivy.settings" file="ivysettings.xml"/>
|
||||
</target>
|
||||
|
||||
<target name="-lib-ivy" depends="-init-ivy" unless="${lib.local}">
|
||||
<ivy:retrieve settingsRef="ivy.settings"
|
||||
pattern="${lib.dir}/[conf]/[type]/[artifact]-[revision].[ext]"
|
||||
conf="compile,runtime"/>
|
||||
</target>
|
||||
|
||||
<target name="-lib-groovy" if="${lib.local}">
|
||||
<copy todir="${build.dir}/lib/runtime/jar">
|
||||
<fileset dir="${env.GROOVY_HOME}/embeddable"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="-lib-local" if="${lib.local}">
|
||||
<echo message="Resolving libraries locally."/>
|
||||
<mkdir dir="${build.dir}/lib/compile/jar"/>
|
||||
<mkdir dir="${build.dir}/lib/runtime/jar"/>
|
||||
<copy todir="${build.dir}/lib/compile/jar" failonerror="false">
|
||||
<fileset dir="${lib.dir}/compile/jar"/>
|
||||
</copy>
|
||||
|
||||
<copy todir="${build.dir}/lib/runtime/jar" failonerror="false">
|
||||
<fileset dir="${lib.dir}/runtime/jar"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<!--======== VERSIONING TARGETS ========-->
|
||||
<target name="increment-build-number" depends="-init">
|
||||
<propertyfile file="${versioning.file}">
|
||||
<entry key="build.number" default="0" type="int" value="1"
|
||||
operation="+"/>
|
||||
</propertyfile>
|
||||
</target>
|
||||
|
||||
<target name="set-version" depends="-init">
|
||||
<input
|
||||
message="The current version is ${version}. Enter a new version: "
|
||||
addproperty="new-version"/>
|
||||
<propertyfile file="${versioning.file}">
|
||||
<entry key="version" value="${new-version}" operation="="
|
||||
type="string"/>
|
||||
<entry key="build.number" value="0" type="int" operation="="/>
|
||||
</propertyfile>
|
||||
</target>
|
||||
|
||||
<!--======== COMPILATION TARGETS ========-->
|
||||
<target name="-compile-groovy" depends="-init,-init-groovy,-lib,-lib-groovy">
|
||||
<mkdir dir="${build.dir}/main/classes"/>
|
||||
<groovyc srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
||||
fork="true" includeAntRuntime="false">
|
||||
|
||||
<classpath>
|
||||
<path refid="groovy.classpath"/>
|
||||
<path refid="compile-libs"/>
|
||||
</classpath>
|
||||
<javac/>
|
||||
</groovyc>
|
||||
</target>
|
||||
|
||||
<target name="-compile-java" depends="-init,-lib">
|
||||
<mkdir dir="${build.dir}/main/classes"/>
|
||||
<javac srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
||||
includeAntRuntime="false" classpathref="compile-libs"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="-compile-groovy"/>
|
||||
|
||||
<!--======== JUNIT TARGETS ========-->
|
||||
<target name="-compile-tests-groovy" depends="-init,compile">
|
||||
<mkdir dir="${build.dir}/test/classes"/>
|
||||
<groovyc srcdir="${src.dir}/test" destdir="${build.dir}/test/classes"
|
||||
includeAntRuntime="false">
|
||||
|
||||
<classpath>
|
||||
<path refid="groovy.classpath"/>
|
||||
<path refid="compile-libs"/>
|
||||
<path location="${build.dir}/main/classes"/>
|
||||
</classpath>
|
||||
</groovyc>
|
||||
</target>
|
||||
|
||||
<target name="-compile-tests-java" depends="-init,compile">
|
||||
<mkdir dir="${build.dir}/test/classes"/>
|
||||
<javac srcdir="${src.dir}/test" destdir="${build.dir}/test/classes"
|
||||
includeAntRuntime="false">
|
||||
<classpath>
|
||||
<path refid="compile-libs"/>
|
||||
<path location="${build.dir}/main/classes"/>
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="compile-tests" depends="-compile-tests-groovy"/>
|
||||
|
||||
<target name="run-tests" depends="compile-tests,resources-test">
|
||||
<junit printsummary="true">
|
||||
<classpath>
|
||||
<path refid="groovy.classpath"/>
|
||||
<path refid="compile-libs"/>
|
||||
<path location="${build.dir}/main/classes"/>
|
||||
<path location="${build.dir}/test/classes"/>
|
||||
</classpath>
|
||||
<formatter type="plain" usefile="false"/>
|
||||
<batchtest>
|
||||
<fileset dir="${build.dir}/test/classes">
|
||||
<include name="**/*"/>
|
||||
</fileset>
|
||||
</batchtest>
|
||||
</junit>
|
||||
</target>
|
||||
|
||||
<!--======== RESOURCES TARGETS ========-->
|
||||
|
||||
<target name="resources" depends="-init">
|
||||
<mkdir dir="${build.dir}/main/classes"/>
|
||||
<copy todir="${build.dir}/main/classes" failonerror="false">
|
||||
<fileset dir="${resources.dir}/main/"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="resources-test" depends="-init">
|
||||
<mkdir dir="${build.dir}/test/classes"/>
|
||||
<copy todir="${build.dir}/test/classes" failonerror="false">
|
||||
<fileset dir="${resources.dir}/test/"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<!--======== BUILD TARGETS ========-->
|
||||
<target name="-build-modular-lib" unless="executable.jar"
|
||||
depends="compile,increment-build-number,resources">
|
||||
|
||||
<jar destfile="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
basedir="${build.dir}/main/classes"/>
|
||||
</target>
|
||||
|
||||
<target name="-build-modular-executable" if="executable.jar"
|
||||
depends="compile,increment-build-number,resources">
|
||||
|
||||
<pathconvert property="jar.classpath" pathsep=" " refid="runtime-libs">
|
||||
<mapper>
|
||||
<chainedmapper>
|
||||
<!-- remove absolute path -->
|
||||
<flattenmapper />
|
||||
|
||||
<!-- add lib/ prefix -->
|
||||
<globmapper from="*" to="lib/*" />
|
||||
</chainedmapper>
|
||||
</mapper>
|
||||
</pathconvert>
|
||||
|
||||
<jar destfile="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
basedir="${build.dir}/main/classes">
|
||||
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="${main.class}"/>
|
||||
<attribute name="Class-Path" value="${jar.classpath}"/>
|
||||
<attribute name="SplashScreen-Image" value="${splash.image}"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="-build-modular"
|
||||
depends="-build-modular-lib,-build-modular-executable"/>
|
||||
|
||||
<target name="-build-packed-libs"
|
||||
depends="compile,increment-build-number,resources">
|
||||
|
||||
<unjar destdir="${build.dir}/main/classes">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar"/>
|
||||
</unjar>
|
||||
|
||||
<jar destfile="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
basedir="${build.dir}/main/classes"/>
|
||||
</target>
|
||||
|
||||
<target name="build" depends="-build-modular"/>
|
||||
|
||||
</project>
|
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.
@ -1,5 +0,0 @@
|
||||
#Mon, 03 Sep 2012 23:38:34 -0500
|
||||
name=time-analyzer
|
||||
version=1.0
|
||||
build.number=11
|
||||
lib.local=true
|
Binary file not shown.
@ -1,84 +0,0 @@
|
||||
import com.jdbernard.timeanalyzer.categories.FilteredCategory
|
||||
import com.jdbernard.timeanalyzer.categories.TimeIntervalCategory
|
||||
import com.jdbernard.timeanalyzer.categorizationplans.DailyCategorizationPlan
|
||||
import com.jdbernard.timeanalyzer.categorizationplans.DescriptionBasedCategorizationPlan
|
||||
import com.jdbernard.timeanalyzer.categorizationplans.TwoLevelCategorizationPlan
|
||||
import com.jdbernard.timeanalyzer.processors.TimelineEventProcessor
|
||||
import com.jdbernard.timestamper.core.FileTimelineSource
|
||||
import org.jfree.chart.ChartFactory
|
||||
import org.jfree.chart.ChartFrame
|
||||
import org.jfree.data.general.DefaultPieDataset
|
||||
import org.jfree.util.SortOrder
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Interval
|
||||
import org.joda.time.Period
|
||||
import org.joda.time.format.PeriodFormat
|
||||
import static com.jdbernard.timeanalyzer.chart.Util.*
|
||||
|
||||
loadEvents = { file, processor ->
|
||||
fileSource = new FileTimelineSource(file.toURI())
|
||||
timeline = fileSource.read()
|
||||
return processor.process(timeline) }
|
||||
|
||||
makePieFrame = { categoryName, category ->
|
||||
def dataset = makePieDataset(category)
|
||||
return new ChartFrame(categoryName,
|
||||
ChartFactory.createPieChart("Time Spent", dataset, true, true, false)) }
|
||||
|
||||
analyze = { file ->
|
||||
def tep = new TimelineEventProcessor()
|
||||
tep.exclusions << /.*Home.*/
|
||||
|
||||
def twoLevelPlan = new TwoLevelCategorizationPlan()
|
||||
def descriptionPlan = new DescriptionBasedCategorizationPlan()
|
||||
|
||||
def setupCat = { cat ->
|
||||
cat.categorizationPlans << twoLevelPlan
|
||||
cat.categorizationPlans << descriptionPlan }
|
||||
|
||||
def dailyPlan = new DailyCategorizationPlan(setupCat)
|
||||
|
||||
def events = loadEvents(file, tep)
|
||||
def topcat = new FilteredCategory("Time Analysis: Jonathan Bernard")
|
||||
topcat.categorizationPlans << dailyPlan
|
||||
topcat.categorizationPlans << twoLevelPlan
|
||||
topcat.categorizationPlans << descriptionPlan
|
||||
|
||||
events.each { if (topcat.matchesEvent(it)) topcat.addEvent(it) }
|
||||
|
||||
return [topcat: topcat, rawEvents: events] }
|
||||
|
||||
listEvents = {topcat ->
|
||||
String result = ""
|
||||
topcat.events.eachWithIndex { event, index ->
|
||||
result += "${index}: ${event}\n" }
|
||||
|
||||
return result }
|
||||
|
||||
listCategories = { topcat ->
|
||||
String result = ""
|
||||
topcat.categories.eachWithIndex { cat, index ->
|
||||
result += "${index}: ${cat}\n" }
|
||||
|
||||
return result }
|
||||
|
||||
details = { topcat ->
|
||||
def result = "Categories\n" + listCategories(topcat)
|
||||
result += "Events\n" + listEvents(topcat)
|
||||
|
||||
return "\n" + result }
|
||||
|
||||
weeklySummary = { events ->
|
||||
def todayMidnight = new DateTime()
|
||||
def friday = todayMidnight.withDayOfWeek(5)
|
||||
def monday = todayMidnight.withDayOfWeek(1)
|
||||
def week = new Interval(monday, Period.days(7))
|
||||
def weekCat = new TimeIntervalCategory(
|
||||
"Week of ${friday.toString('MMM dd, yyyy')}", week)
|
||||
|
||||
weekCat.categorizationPlans << new TwoLevelCategorizationPlan()
|
||||
weekCat.categorizationPlans << new DescriptionBasedCategorizationPlan()
|
||||
|
||||
events.each { if (weekCat.matchesEvent(it)) weekCat.addEvent(it) }
|
||||
|
||||
return weekCat }
|
@ -1,121 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
import org.joda.time.Duration
|
||||
import org.joda.time.format.PeriodFormat
|
||||
import org.joda.time.format.PeriodFormatter
|
||||
|
||||
/**
|
||||
* A `Category` represents a collection of like `Events` and sub-categories.
|
||||
*/
|
||||
public abstract class Category implements Comparable<Category> {
|
||||
|
||||
public static PeriodFormatter periodFormatter = PeriodFormat.getDefault()
|
||||
|
||||
/** List of events directly under this category. */
|
||||
public List events
|
||||
|
||||
/** List of sub-categories under this category. */
|
||||
public List categories
|
||||
|
||||
/** List of `CategorizationPlan`s to use when adding new `Event`s to the
|
||||
* category. */
|
||||
public List categorizationPlans
|
||||
|
||||
/** A end-user-friendly text description of the category.*/
|
||||
public String description
|
||||
|
||||
public Category() { this("Unamed Category") }
|
||||
|
||||
public Category(String description) {
|
||||
events = []
|
||||
categories = []
|
||||
categorizationPlans = []
|
||||
this.description = description }
|
||||
|
||||
/**
|
||||
* Does the given event belong in this category?
|
||||
* @param e `Event` being considered.
|
||||
* @return **`true`** if this event belongs in this category, **`false`**
|
||||
* otherwise.
|
||||
*/
|
||||
public abstract boolean matchesEvent(Event e)
|
||||
|
||||
/**
|
||||
* Add an event to this category. This method does not check to see if the
|
||||
* `Event` matches this category. It assumed that the check has already been
|
||||
* made and the `Event` matches.*/
|
||||
public Event addEvent(Event event) {
|
||||
|
||||
def addedEvent
|
||||
|
||||
// Try first to add it to a subcategory (or create a new subcategory).
|
||||
addedEvent = addToSubcategory(event)
|
||||
|
||||
// Cannot add to a subcategory, add to ourselves.
|
||||
if (!addedEvent) {
|
||||
events << event
|
||||
addedEvent = event }
|
||||
|
||||
return addedEvent }
|
||||
|
||||
public Event addToSubcategory(Event e) {
|
||||
|
||||
// find all matching subcategories
|
||||
def matchingCategories = categories.findAll { it.matchesEvent(e) }
|
||||
|
||||
if (matchingCategories) {
|
||||
return matchingCategories[0].addEvent(e) }
|
||||
|
||||
// no matching subcategories, can we create a new one based on one
|
||||
// of our plans?
|
||||
def matchingPlans = categorizationPlans.findAll {
|
||||
it.deservesNewCategory(e, events) }
|
||||
|
||||
if (matchingPlans) {
|
||||
// create the new category
|
||||
def newCategory = matchingPlans[0].newCategory(e, events)
|
||||
|
||||
// add it to our list of cateogries
|
||||
categories << newCategory
|
||||
|
||||
// add the new event to the category
|
||||
def addedEvent = newCategory.addEvent(e)
|
||||
|
||||
// move all the events that match the new category over
|
||||
def existingEvents = matchingPlans[0].findEventsToRecategorize(e, events)
|
||||
events -= existingEvents
|
||||
existingEvents.each { newCategory.addEvent(it) }
|
||||
|
||||
// return the new entry
|
||||
return addedEvent }
|
||||
|
||||
return null }
|
||||
|
||||
public Category filter(List<CategoryFilter> filters) {
|
||||
|
||||
// create new filtered category
|
||||
FilteredCategory fc = new FilteredCategory(description)
|
||||
fc.filters = filters
|
||||
|
||||
// filter all events and add them to the category
|
||||
fc.events
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
public Category filter(CategoryFilter filter) { return filter([filter]) }
|
||||
|
||||
public Category filter(Closure c) { return filter(c as CategoryFilter) }
|
||||
|
||||
public Duration getDuration() {
|
||||
return categories.sum(new Duration(0)) { it.duration } +
|
||||
events.sum(new Duration(0)) { it.duration } }
|
||||
|
||||
public int compareTo(Category other) {
|
||||
return this.getDuration().compareTo(other.getDuration()) }
|
||||
|
||||
public String toString() {
|
||||
String period = periodFormatter.print(this.duration.toPeriod())
|
||||
return "${description} (${period})" }
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories;
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event;
|
||||
|
||||
public interface CategoryFilter {
|
||||
public boolean matchesEvent(Event event);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class DescriptionBasedCategory extends Category {
|
||||
|
||||
public DescriptionBasedCategory(String description) {
|
||||
super()
|
||||
this.description = description.replaceAll(/\p{Punct}/, '') }
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
return e.description.replaceAll(/\p{Punct}/, '').toLowerCase() ==
|
||||
description.toLowerCase() }
|
||||
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class FilteredCategory extends Category {
|
||||
|
||||
List<CategoryFilter> filters = []
|
||||
|
||||
public FilteredCategory(String description) {
|
||||
super(description) }
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
return filters.every { filter -> filter.matchesEvent(e) } }
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class GeneralCategory extends Category {
|
||||
|
||||
public GeneralCategory() { this("General") }
|
||||
|
||||
public GeneralCategory(String description) { super(description) }
|
||||
|
||||
public boolean matchesEvent(Event e) { true }
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
import org.joda.time.Interval
|
||||
|
||||
public class TimeIntervalCategory extends Category {
|
||||
|
||||
private final Interval interval
|
||||
|
||||
public TimeIntervalCategory(String desc, Interval interval) {
|
||||
super(desc)
|
||||
this.interval = interval }
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
Interval eventIv = new Interval(e.start, e.duration)
|
||||
return interval.contains(eventIv) }
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
import org.joda.time.Interval
|
||||
import org.joda.time.ReadableInstant
|
||||
import org.joda.time.ReadableInterval
|
||||
|
||||
public class TimeIntervalCategoryFilter implements CategoryFilter {
|
||||
|
||||
ReadableInterval interval
|
||||
|
||||
public TimeIntervalCategoryFilter(ReadableInterval interval) {
|
||||
this.interval = interval }
|
||||
|
||||
public TimeIntervalCategoryFilter(ReadableInstant start,
|
||||
ReadableInstant end) {
|
||||
this.interval = new Interval(start, end) }
|
||||
|
||||
public boolean matchesEvent(Event event) {
|
||||
return interval.contains(event.start) }
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categories
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class TwoLevelCategory extends Category {
|
||||
|
||||
private final def descriptionPattern
|
||||
|
||||
public TwoLevelCategory(String heading) {
|
||||
super(heading)
|
||||
|
||||
descriptionPattern = ~/^${heading}:\s*(.*)/ }
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
return e.description ==~ descriptionPattern }
|
||||
|
||||
public Event addEvent(Event e) {
|
||||
def m = e.description =~ descriptionPattern
|
||||
|
||||
e = new Event(e, description: m[0][1])
|
||||
|
||||
super.addEvent(e) }
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categorizationplans;
|
||||
|
||||
import com.jdbernard.timeanalyzer.categories.Category;
|
||||
import com.jdbernard.timeanalyzer.events.Event;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class CategorizationPlan {
|
||||
|
||||
public CategorizationPlan() {}
|
||||
public CategorizationPlan(Closure newCatSetupFun) {
|
||||
newCategorySetupFun = newCatSetupFun }
|
||||
|
||||
protected Closure newCategorySetupFun
|
||||
|
||||
protected void setupNewCategory(Category cat) {
|
||||
if (newCategorySetupFun) newCategorySetupFun(cat) }
|
||||
|
||||
|
||||
public abstract boolean deservesNewCategory(Event event, List<Event> existingEvents)
|
||||
public abstract Category newCategory(Event event, List<Event> existingEvents)
|
||||
public abstract List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categorizationplans
|
||||
|
||||
import com.jdbernard.timeanalyzer.categories.Category
|
||||
import com.jdbernard.timeanalyzer.categories.TimeIntervalCategory
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Interval
|
||||
import org.joda.time.Period
|
||||
|
||||
public class DailyCategorizationPlan extends CategorizationPlan {
|
||||
|
||||
public DailyCategorizationPlan() {}
|
||||
public DailyCategorizationPlan(Closure newCatSetupFun) {
|
||||
super(newCatSetupFun) }
|
||||
|
||||
boolean deservesNewCategory(Event event, List<Event> existingEvents) {
|
||||
Interval fullDay = new Interval(
|
||||
event.start.toDateMidnight(), Period.days(1))
|
||||
Interval eventIv = new Interval(
|
||||
event.start, event.duration)
|
||||
|
||||
return fullDay.contains(eventIv) }
|
||||
|
||||
Category newCategory(Event event, List<Event> existingEvents) {
|
||||
Interval fullday = new Interval(
|
||||
event.start.toDateMidnight(), Period.days(1))
|
||||
|
||||
Category newCat = new TimeIntervalCategory(
|
||||
event.start.toString("EEE, MMM dd"), fullday)
|
||||
|
||||
setupNewCategory(newCat)
|
||||
|
||||
return newCat }
|
||||
|
||||
List<Event> findEventsToRecategorize(Event event,
|
||||
List<Event> existingEvents) {
|
||||
Interval fullday = new Interval(
|
||||
event.start.toDateMidnight(), Period.days(1))
|
||||
|
||||
return existingEvents.findAll {
|
||||
Interval iv = new Interval(it.start, it.duration)
|
||||
fullday.contains(iv) } }
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categorizationplans
|
||||
|
||||
import com.jdbernard.timeanalyzer.categories.Category
|
||||
import com.jdbernard.timeanalyzer.categories.DescriptionBasedCategory
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class DescriptionBasedCategorizationPlan extends CategorizationPlan {
|
||||
|
||||
public DescriptionBasedCategorizationPlan() {}
|
||||
public DescriptionBasedCategorizationPlan(Closure newCatSetupFun) {
|
||||
super(newCatSetupFun) }
|
||||
|
||||
public boolean deservesNewCategory(Event event, List<Event> existingEvents) {
|
||||
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
|
||||
return existingEvents.any {
|
||||
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } }
|
||||
|
||||
public Category newCategory(Event event,
|
||||
List<Event> existingEvents) {
|
||||
def newCat = new DescriptionBasedCategory(event.description)
|
||||
setupNewCategory(newCat)
|
||||
return newCat }
|
||||
|
||||
public List<Event> findEventsToRecategorize(Event event,
|
||||
List<Event> existingEvents) {
|
||||
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
|
||||
return existingEvents.findAll {
|
||||
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } }
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.categorizationplans
|
||||
|
||||
import com.jdbernard.timeanalyzer.categories.Category
|
||||
import com.jdbernard.timeanalyzer.categories.TwoLevelCategory
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class TwoLevelCategorizationPlan extends CategorizationPlan {
|
||||
|
||||
private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/
|
||||
|
||||
public TwoLevelCategorizationPlan() {}
|
||||
public TwoLevelCategorizationPlan(Closure newCatSetupFun) {
|
||||
super(newCatSetupFun) }
|
||||
|
||||
public boolean deservesNewCategory(Event event, List<Event> el) {
|
||||
return event ==~ TWO_LEVEL_PATTERN }
|
||||
|
||||
public Category newCategory(Event event, List<Event> el) {
|
||||
def m = event.description =~ TWO_LEVEL_PATTERN
|
||||
def newCat = new TwoLevelCategory(m[0][1])
|
||||
setupNewCategory(newCat)
|
||||
return newCat }
|
||||
|
||||
public List<Event> findEventsToRecategorize(Event event,
|
||||
List<Event> existingEvents) {
|
||||
def m = event.description =~ TWO_LEVEL_PATTERN
|
||||
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ } }
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.chart
|
||||
|
||||
import com.jdbernard.timeanalyzer.categories.Category
|
||||
import org.jfree.data.general.DefaultPieDataset
|
||||
import org.jfree.data.general.PieDataset
|
||||
import org.jfree.util.SortOrder
|
||||
|
||||
public class Util {
|
||||
|
||||
public static PieDataset makePieDataset(Category category) {
|
||||
DefaultPieDataset dpds = new DefaultPieDataset()
|
||||
category.categories.each { cat ->
|
||||
dpds.setValue(cat.description, cat.duration.standardSeconds) }
|
||||
category.events.each { entry ->
|
||||
dpds.setValue(entry.description, entry.duration.standardSeconds) }
|
||||
dpds.sortByValues(SortOrder.DESCENDING)
|
||||
return dpds }
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.events
|
||||
|
||||
import org.joda.time.ReadableDateTime
|
||||
import org.joda.time.Duration
|
||||
import org.joda.time.format.PeriodFormat
|
||||
import org.joda.time.format.PeriodFormatter
|
||||
|
||||
public class Event implements Cloneable {
|
||||
|
||||
public static PeriodFormatter periodFormatter = PeriodFormat.getDefault()
|
||||
|
||||
public final String description
|
||||
public final String notes
|
||||
public final ReadableDateTime start
|
||||
public Duration duration // should be final, allows modification for the
|
||||
// TimelineEventProcessor
|
||||
|
||||
public Event(String desc, String notes, ReadableDateTime start, Duration duration) {
|
||||
this.description = desc
|
||||
this.notes = notes
|
||||
this.start = start
|
||||
this.duration = duration }
|
||||
|
||||
public Event(Map params) {
|
||||
this.description = params.description ?: ""
|
||||
this.notes = params.notes ?: ""
|
||||
this.start = params.start
|
||||
this.duration = params.duration }
|
||||
|
||||
public Event(Map params, Event e) {
|
||||
this.description = params.description ?: e.description
|
||||
this.notes = params.notes ?: e.notes
|
||||
this.start = params.start ?: e.start
|
||||
this.duration = params.duration ?: e.duration }
|
||||
|
||||
public String toString() {
|
||||
String period = periodFormatter.print(this.duration.toPeriod())
|
||||
return "${description} (${period})" }
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package com.jdbernard.timeanalyzer.processors
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
import com.jdbernard.timestamper.core.Timeline
|
||||
import com.jdbernard.timestamper.core.TimelineProperties
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Duration
|
||||
|
||||
public class TimelineEventProcessor {
|
||||
|
||||
/** Events whose description matches one of these regex strings or
|
||||
* patterns are ignored. */
|
||||
List exclusions = []
|
||||
|
||||
public TimelineEventProcessor() {}
|
||||
|
||||
public TimelineEventProcessor(List exclusions) { this.exclusions = exclusions }
|
||||
|
||||
public List<Event> process(File timelinePropFile) {
|
||||
def timelineProps = new TimelineProperties(timelinePropFile)
|
||||
return process(timelineProps.timeline) }
|
||||
|
||||
public List<Event> process(Timeline timeline) {
|
||||
List<Event> events = []
|
||||
|
||||
timeline.each { marker ->
|
||||
Event e = new Event(
|
||||
description: marker.mark,
|
||||
notes: marker.notes,
|
||||
start: new DateTime(marker.timestamp),
|
||||
duration: new Duration(0))
|
||||
|
||||
println e
|
||||
|
||||
// if this is not the first event, then we need to update the
|
||||
// duration of the previous event
|
||||
if (events.size > 0) {
|
||||
Event lastEvent = events[-1]
|
||||
lastEvent.duration = new Duration(lastEvent.start, e.start) }
|
||||
|
||||
events << e }
|
||||
|
||||
def excluded = events.findAll { event ->
|
||||
exclusions.any { exclusion -> event.description ==~ exclusion } }
|
||||
|
||||
return events - excluded }
|
||||
|
||||
public static List<Event> process(def timeline, List exclusions) {
|
||||
def inst = new TimelineEventProcessor(exclusions)
|
||||
return inst.process(timeline) }
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package com.quantumdigital.ithelp.timeanalyzer
|
||||
|
||||
import com.jdbernard.timeanalyzer.categories.Category
|
||||
import com.jdbernard.timeanalyzer.categorizationplans.CategorizationPlan
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class TicketCategorizationPlan extends CategorizationPlan {
|
||||
|
||||
private static def TICKET_PATTERN = ~/.*#(\d+).*/
|
||||
|
||||
public boolean deservesNewCategory(Event e, List<Event> el) {
|
||||
return e.description ==~ TICKET_PATTERN
|
||||
}
|
||||
|
||||
public Category newCategory(Event e, List<Event> el) {
|
||||
def m = e.description =~ TICKET_PATTERN
|
||||
|
||||
def newCat = new TicketCategory(m[0][1] as int)
|
||||
|
||||
setupNewCategory(newCat)
|
||||
|
||||
return newCat
|
||||
}
|
||||
|
||||
public List<Event> findEventsToRecategorize(Event e,
|
||||
List<Event> existingEvents) {
|
||||
def m = e.description =~ TICKET_PATTERN
|
||||
int ticketId = m[0][1] as int
|
||||
|
||||
return existingEvents.findAll {
|
||||
it.description ==~ /.*#${ticketId}.*/ }
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.quantumdigital.ithelp.timeanalyzer
|
||||
|
||||
import com.jdbernard.timeanalyzer.categories.Category
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class TicketCategory extends Category {
|
||||
|
||||
public final int ticketId
|
||||
|
||||
public TicketCategory(int ticketId) {
|
||||
super()
|
||||
this.ticketId = ticketId
|
||||
this.description = "Ticket #${ticketId}"
|
||||
}
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
return (e.description ==~ /.*#${ticketId}.*/)
|
||||
}
|
||||
|
||||
public Event addEvent(Event e) {
|
||||
TicketEvent te = new TicketEvent(e)
|
||||
events << te
|
||||
return te
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.quantumdigital.ithelp.timeanalyzer
|
||||
|
||||
import com.jdbernard.timeanalyzer.events.Event
|
||||
|
||||
public class TicketEvent extends Event {
|
||||
|
||||
public final int id
|
||||
|
||||
public TicketEvent(String desc, String notes, String start, String duration) {
|
||||
|
||||
super(desc, notes, start, duration)
|
||||
|
||||
def m = desc =~ /.*#(\d+).*/
|
||||
this.id = m[0][1] as int
|
||||
}
|
||||
|
||||
public TicketEvent(Map params) {
|
||||
super(params)
|
||||
|
||||
def m = description =~ /.*#(\d+).*/
|
||||
this.id = m[0][1] as int
|
||||
}
|
||||
|
||||
public TicketEvent(Map params, Event e) {
|
||||
super(params, e)
|
||||
|
||||
def m = description =~ /.*#(\d+).*/
|
||||
this.id = m[0][1] as int
|
||||
}
|
||||
|
||||
public TicketEvent(Event e) {
|
||||
super([:], e)
|
||||
|
||||
def m = description =~ /.*#(\d+).*/
|
||||
this.id = m[0][1] as int
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
my @files=`ls vim-views/ | grep view`;
|
||||
my $cwd=`pwd`;
|
||||
chomp($cwd);
|
||||
|
||||
$cwd=~s!(.*?)/VBS!$1!;
|
||||
|
||||
print $cwd;
|
||||
|
||||
chdir("vim-views");
|
||||
|
||||
foreach my $file (@files) {
|
||||
chomp($file);
|
||||
system("mv", "$file", "$file.bak");
|
||||
open(IN,"<$file.bak");
|
||||
open(OUT, ">$file");
|
||||
|
||||
while(<IN>) {
|
||||
s!(edit\s).*?(/VBS.*)!$1$cwd$2!;
|
||||
print OUT;
|
||||
}
|
||||
|
||||
close(IN);
|
||||
close(OUT);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
perl -pi -e 's/[\t\r\f ]+$//g' $@
|
||||
|
||||
for file in $@
|
||||
do
|
||||
rm "$file.bak"
|
||||
echo "$file done!"
|
||||
done
|
@ -1,20 +0,0 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
my $lc=0;
|
||||
my $flc;
|
||||
my $filename;
|
||||
while(<>) {
|
||||
$filename=$_;
|
||||
chomp($filename);
|
||||
open(IN,"<$filename");
|
||||
|
||||
$flc=0;
|
||||
while(<IN>) {
|
||||
$flc++;
|
||||
$lc++;
|
||||
}
|
||||
|
||||
print "$filename: $flc\n";
|
||||
}
|
||||
|
||||
print "Total: $lc\n";
|
@ -1,8 +0,0 @@
|
||||
set viewoptions=cursor,folds,options,slash,unix
|
||||
|
||||
let ideHome = $PWD
|
||||
augroup ide
|
||||
au BufWinEnter *.java,*.xml,*.scss,*.yaws,*.html,*.js execute "source ".ideHome."/.ide/vim-views/".strpart(bufname("%"), strridx(bufname("%"), "/") + 1).".view"
|
||||
au BufWinLeave *.java,*.xml,*.scss,*.yaws,*.html,*.js execute "mkview! ".ideHome."/.ide/vim-views/".strpart(bufname("%"), strridx(bufname("%"), "/") + 1).".view"
|
||||
au BufWinLeave *.java,*.xml,*.scss,*.yaws,*.html,*.js execute "silent !echo 'syntax on' >> ".ideHome."/.ide/vim-views/".strpart(bufname("%"), strridx(bufname("%"), "/") + 1).".view"
|
||||
augroup END
|
@ -1,108 +0,0 @@
|
||||
let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
|
||||
argglobal
|
||||
edit /mnt/secure/projects/jdb-labs/timestamper/web-app/www/index.yaws
|
||||
setlocal keymap=
|
||||
setlocal noarabic
|
||||
setlocal autoindent
|
||||
setlocal balloonexpr=
|
||||
setlocal nobinary
|
||||
setlocal bufhidden=
|
||||
setlocal buflisted
|
||||
setlocal buftype=
|
||||
setlocal nocindent
|
||||
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
|
||||
setlocal cinoptions=
|
||||
setlocal cinwords=if,else,while,do,for,switch
|
||||
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
|
||||
setlocal commentstring=/*%s*/
|
||||
setlocal complete=.,w,b,u,t,i
|
||||
setlocal completefunc=
|
||||
setlocal nocopyindent
|
||||
setlocal nocursorcolumn
|
||||
setlocal nocursorline
|
||||
setlocal define=
|
||||
setlocal dictionary=
|
||||
setlocal nodiff
|
||||
setlocal equalprg=
|
||||
setlocal errorformat=
|
||||
setlocal expandtab
|
||||
if &filetype != 'erlang'
|
||||
setlocal filetype=erlang
|
||||
endif
|
||||
setlocal foldcolumn=0
|
||||
setlocal foldenable
|
||||
setlocal foldexpr=0
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldtext=foldtext()
|
||||
setlocal formatexpr=
|
||||
setlocal formatoptions=tcq
|
||||
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||
setlocal grepprg=
|
||||
setlocal iminsert=2
|
||||
setlocal imsearch=2
|
||||
setlocal include=
|
||||
setlocal includeexpr=
|
||||
setlocal indentexpr=
|
||||
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
|
||||
setlocal noinfercase
|
||||
setlocal iskeyword=@,48-57,_,192-255
|
||||
setlocal keywordprg=
|
||||
setlocal nolinebreak
|
||||
setlocal nolisp
|
||||
setlocal nolist
|
||||
setlocal makeprg=
|
||||
setlocal matchpairs=(:),{:},[:]
|
||||
setlocal nomodeline
|
||||
setlocal modifiable
|
||||
setlocal nrformats=octal,hex
|
||||
setlocal number
|
||||
setlocal numberwidth=4
|
||||
setlocal omnifunc=
|
||||
setlocal path=
|
||||
setlocal nopreserveindent
|
||||
setlocal nopreviewwindow
|
||||
setlocal quoteescape=\\
|
||||
setlocal noreadonly
|
||||
setlocal norightleft
|
||||
setlocal rightleftcmd=search
|
||||
setlocal noscrollbind
|
||||
setlocal shiftwidth=4
|
||||
setlocal noshortname
|
||||
setlocal nosmartindent
|
||||
setlocal softtabstop=0
|
||||
setlocal nospell
|
||||
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||
setlocal spellfile=
|
||||
setlocal spelllang=en
|
||||
setlocal statusline=
|
||||
setlocal suffixesadd=
|
||||
setlocal swapfile
|
||||
setlocal synmaxcol=3000
|
||||
if &syntax != 'erlang'
|
||||
setlocal syntax=erlang
|
||||
endif
|
||||
setlocal tabstop=4
|
||||
setlocal tags=
|
||||
setlocal textwidth=80
|
||||
setlocal thesaurus=
|
||||
setlocal nowinfixheight
|
||||
setlocal nowinfixwidth
|
||||
setlocal wrap
|
||||
setlocal wrapmargin=0
|
||||
silent! normal! zE
|
||||
let s:l = 104 - ((59 * winheight(0) + 35) / 71)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
exe s:l
|
||||
normal! zt
|
||||
104
|
||||
normal! 038l
|
||||
lcd /mnt/secure/projects/jdb-labs/timestamper/web-app/www
|
||||
let &so = s:so_save | let &siso = s:siso_save
|
||||
doautoall SessionLoadPost
|
||||
" vim: set ft=vim :
|
||||
syntax on
|
@ -1,107 +0,0 @@
|
||||
let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
|
||||
argglobal
|
||||
edit /mnt/secure/projects/jdb-labs/timestamper/web-app/www/css/ts-screen.scss
|
||||
setlocal keymap=
|
||||
setlocal noarabic
|
||||
setlocal autoindent
|
||||
setlocal balloonexpr=
|
||||
setlocal nobinary
|
||||
setlocal bufhidden=
|
||||
setlocal buflisted
|
||||
setlocal buftype=
|
||||
setlocal nocindent
|
||||
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
|
||||
setlocal cinoptions=
|
||||
setlocal cinwords=if,else,while,do,for,switch
|
||||
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
|
||||
setlocal commentstring=/*%s*/
|
||||
setlocal complete=.,w,b,u,t,i
|
||||
setlocal completefunc=
|
||||
setlocal nocopyindent
|
||||
setlocal nocursorcolumn
|
||||
setlocal nocursorline
|
||||
setlocal define=
|
||||
setlocal dictionary=
|
||||
setlocal nodiff
|
||||
setlocal equalprg=
|
||||
setlocal errorformat=
|
||||
setlocal expandtab
|
||||
if &filetype != ''
|
||||
setlocal filetype=
|
||||
endif
|
||||
setlocal foldcolumn=0
|
||||
setlocal foldenable
|
||||
setlocal foldexpr=0
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldtext=foldtext()
|
||||
setlocal formatexpr=
|
||||
setlocal formatoptions=tcq
|
||||
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||
setlocal grepprg=
|
||||
setlocal iminsert=2
|
||||
setlocal imsearch=2
|
||||
setlocal include=
|
||||
setlocal includeexpr=
|
||||
setlocal indentexpr=
|
||||
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
|
||||
setlocal noinfercase
|
||||
setlocal iskeyword=@,48-57,_,192-255
|
||||
setlocal keywordprg=
|
||||
setlocal nolinebreak
|
||||
setlocal nolisp
|
||||
setlocal nolist
|
||||
setlocal makeprg=
|
||||
setlocal matchpairs=(:),{:},[:]
|
||||
setlocal nomodeline
|
||||
setlocal modifiable
|
||||
setlocal nrformats=octal,hex
|
||||
setlocal number
|
||||
setlocal numberwidth=4
|
||||
setlocal omnifunc=
|
||||
setlocal path=
|
||||
setlocal nopreserveindent
|
||||
setlocal nopreviewwindow
|
||||
setlocal quoteescape=\\
|
||||
setlocal noreadonly
|
||||
setlocal norightleft
|
||||
setlocal rightleftcmd=search
|
||||
setlocal noscrollbind
|
||||
setlocal shiftwidth=4
|
||||
setlocal noshortname
|
||||
setlocal nosmartindent
|
||||
setlocal softtabstop=0
|
||||
setlocal nospell
|
||||
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||
setlocal spellfile=
|
||||
setlocal spelllang=en
|
||||
setlocal statusline=
|
||||
setlocal suffixesadd=
|
||||
setlocal swapfile
|
||||
setlocal synmaxcol=3000
|
||||
if &syntax != 'sass'
|
||||
setlocal syntax=sass
|
||||
endif
|
||||
setlocal tabstop=4
|
||||
setlocal tags=
|
||||
setlocal textwidth=80
|
||||
setlocal thesaurus=
|
||||
setlocal nowinfixheight
|
||||
setlocal nowinfixwidth
|
||||
setlocal wrap
|
||||
setlocal wrapmargin=0
|
||||
silent! normal! zE
|
||||
let s:l = 392 - ((46 * winheight(0) + 35) / 71)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
exe s:l
|
||||
normal! zt
|
||||
392
|
||||
normal! 022l
|
||||
let &so = s:so_save | let &siso = s:siso_save
|
||||
doautoall SessionLoadPost
|
||||
" vim: set ft=vim :
|
||||
syntax on
|
@ -1,143 +0,0 @@
|
||||
let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
|
||||
argglobal
|
||||
edit /mnt/secure/projects/jdb-labs/timestamper/web-app/www/js/ts.js
|
||||
setlocal keymap=
|
||||
setlocal noarabic
|
||||
setlocal autoindent
|
||||
setlocal balloonexpr=
|
||||
setlocal nobinary
|
||||
setlocal bufhidden=
|
||||
setlocal buflisted
|
||||
setlocal buftype=
|
||||
setlocal nocindent
|
||||
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
|
||||
setlocal cinoptions=
|
||||
setlocal cinwords=if,else,while,do,for,switch
|
||||
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
|
||||
setlocal commentstring=/*%s*/
|
||||
setlocal complete=.,w,b,u,t,i
|
||||
setlocal completefunc=
|
||||
setlocal nocopyindent
|
||||
setlocal nocursorcolumn
|
||||
setlocal nocursorline
|
||||
setlocal define=
|
||||
setlocal dictionary=
|
||||
setlocal nodiff
|
||||
setlocal equalprg=
|
||||
setlocal errorformat=
|
||||
setlocal expandtab
|
||||
if &filetype != 'javascript'
|
||||
setlocal filetype=javascript
|
||||
endif
|
||||
setlocal foldcolumn=0
|
||||
setlocal foldenable
|
||||
setlocal foldexpr=0
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldtext=foldtext()
|
||||
setlocal formatexpr=
|
||||
setlocal formatoptions=tcq
|
||||
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||
setlocal grepprg=
|
||||
setlocal iminsert=2
|
||||
setlocal imsearch=2
|
||||
setlocal include=
|
||||
setlocal includeexpr=
|
||||
setlocal indentexpr=
|
||||
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
|
||||
setlocal noinfercase
|
||||
setlocal iskeyword=@,48-57,_,192-255
|
||||
setlocal keywordprg=
|
||||
setlocal nolinebreak
|
||||
setlocal nolisp
|
||||
setlocal nolist
|
||||
setlocal makeprg=
|
||||
setlocal matchpairs=(:),{:},[:]
|
||||
setlocal nomodeline
|
||||
setlocal modifiable
|
||||
setlocal nrformats=octal,hex
|
||||
setlocal number
|
||||
setlocal numberwidth=4
|
||||
setlocal omnifunc=
|
||||
setlocal path=
|
||||
setlocal nopreserveindent
|
||||
setlocal nopreviewwindow
|
||||
setlocal quoteescape=\\
|
||||
setlocal noreadonly
|
||||
setlocal norightleft
|
||||
setlocal rightleftcmd=search
|
||||
setlocal noscrollbind
|
||||
setlocal shiftwidth=4
|
||||
setlocal noshortname
|
||||
setlocal nosmartindent
|
||||
setlocal softtabstop=0
|
||||
setlocal nospell
|
||||
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||
setlocal spellfile=
|
||||
setlocal spelllang=en
|
||||
setlocal statusline=
|
||||
setlocal suffixesadd=
|
||||
setlocal swapfile
|
||||
setlocal synmaxcol=3000
|
||||
if &syntax != 'javascript'
|
||||
setlocal syntax=javascript
|
||||
endif
|
||||
setlocal tabstop=4
|
||||
setlocal tags=
|
||||
setlocal textwidth=80
|
||||
setlocal thesaurus=
|
||||
setlocal nowinfixheight
|
||||
setlocal nowinfixwidth
|
||||
setlocal wrap
|
||||
setlocal wrapmargin=0
|
||||
silent! normal! zE
|
||||
9,18fold
|
||||
20,28fold
|
||||
30,43fold
|
||||
45,62fold
|
||||
64,82fold
|
||||
87,289fold
|
||||
291,378fold
|
||||
380,448fold
|
||||
450,487fold
|
||||
489,603fold
|
||||
605,664fold
|
||||
666,702fold
|
||||
9
|
||||
normal zc
|
||||
20
|
||||
normal zc
|
||||
30
|
||||
normal zc
|
||||
45
|
||||
normal zo
|
||||
64
|
||||
normal zc
|
||||
87
|
||||
normal zc
|
||||
291
|
||||
normal zo
|
||||
380
|
||||
normal zc
|
||||
450
|
||||
normal zc
|
||||
489
|
||||
normal zo
|
||||
605
|
||||
normal zc
|
||||
666
|
||||
normal zc
|
||||
let s:l = 334 - ((273 * winheight(0) + 35) / 71)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
exe s:l
|
||||
normal! zt
|
||||
334
|
||||
normal! 042l
|
||||
let &so = s:so_save | let &siso = s:siso_save
|
||||
doautoall SessionLoadPost
|
||||
" vim: set ft=vim :
|
||||
syntax on
|
@ -1,185 +0,0 @@
|
||||
let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
|
||||
argglobal
|
||||
edit /mnt/secure/projects/jdb-labs/timestamper/web-app/src/ts_api.erl
|
||||
setlocal keymap=
|
||||
setlocal noarabic
|
||||
setlocal autoindent
|
||||
setlocal balloonexpr=
|
||||
setlocal nobinary
|
||||
setlocal bufhidden=
|
||||
setlocal buflisted
|
||||
setlocal buftype=
|
||||
setlocal nocindent
|
||||
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
|
||||
setlocal cinoptions=
|
||||
setlocal cinwords=if,else,while,do,for,switch
|
||||
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
|
||||
setlocal commentstring=/*%s*/
|
||||
setlocal complete=.,w,b,u,t,i
|
||||
setlocal completefunc=
|
||||
setlocal nocopyindent
|
||||
setlocal nocursorcolumn
|
||||
setlocal nocursorline
|
||||
setlocal define=
|
||||
setlocal dictionary=
|
||||
setlocal nodiff
|
||||
setlocal equalprg=
|
||||
setlocal errorformat=
|
||||
setlocal expandtab
|
||||
if &filetype != 'erlang'
|
||||
setlocal filetype=erlang
|
||||
endif
|
||||
setlocal foldcolumn=0
|
||||
setlocal foldenable
|
||||
setlocal foldexpr=0
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldtext=foldtext()
|
||||
setlocal formatexpr=
|
||||
setlocal formatoptions=tcq
|
||||
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||
setlocal grepprg=
|
||||
setlocal iminsert=2
|
||||
setlocal imsearch=2
|
||||
setlocal include=
|
||||
setlocal includeexpr=
|
||||
setlocal indentexpr=
|
||||
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
|
||||
setlocal noinfercase
|
||||
setlocal iskeyword=@,48-57,_,192-255
|
||||
setlocal keywordprg=
|
||||
setlocal nolinebreak
|
||||
setlocal nolisp
|
||||
setlocal nolist
|
||||
setlocal makeprg=
|
||||
setlocal matchpairs=(:),{:},[:]
|
||||
setlocal nomodeline
|
||||
setlocal modifiable
|
||||
setlocal nrformats=octal,hex
|
||||
setlocal number
|
||||
setlocal numberwidth=4
|
||||
setlocal omnifunc=
|
||||
setlocal path=
|
||||
setlocal nopreserveindent
|
||||
setlocal nopreviewwindow
|
||||
setlocal quoteescape=\\
|
||||
setlocal noreadonly
|
||||
setlocal norightleft
|
||||
setlocal rightleftcmd=search
|
||||
setlocal noscrollbind
|
||||
setlocal shiftwidth=4
|
||||
setlocal noshortname
|
||||
setlocal nosmartindent
|
||||
setlocal softtabstop=0
|
||||
setlocal nospell
|
||||
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||
setlocal spellfile=
|
||||
setlocal spelllang=en
|
||||
setlocal statusline=
|
||||
setlocal suffixesadd=
|
||||
setlocal swapfile
|
||||
setlocal synmaxcol=3000
|
||||
if &syntax != 'erlang'
|
||||
setlocal syntax=erlang
|
||||
endif
|
||||
setlocal tabstop=4
|
||||
setlocal tags=
|
||||
setlocal textwidth=80
|
||||
setlocal thesaurus=
|
||||
setlocal nowinfixheight
|
||||
setlocal nowinfixwidth
|
||||
setlocal wrap
|
||||
setlocal wrapmargin=0
|
||||
silent! normal! zE
|
||||
7,28fold
|
||||
35,51fold
|
||||
55,73fold
|
||||
77,92fold
|
||||
96,126fold
|
||||
130,161fold
|
||||
167,196fold
|
||||
198,202fold
|
||||
204,239fold
|
||||
241,248fold
|
||||
250,267fold
|
||||
269,301fold
|
||||
303,310fold
|
||||
312,331fold
|
||||
335,421fold
|
||||
423,429fold
|
||||
431,453fold
|
||||
455,469fold
|
||||
471,486fold
|
||||
492,499fold
|
||||
502,506fold
|
||||
508,515fold
|
||||
517,525fold
|
||||
528,539fold
|
||||
541,552fold
|
||||
554,563fold
|
||||
7
|
||||
normal zc
|
||||
35
|
||||
normal zc
|
||||
55
|
||||
normal zc
|
||||
77
|
||||
normal zc
|
||||
96
|
||||
normal zc
|
||||
130
|
||||
normal zc
|
||||
167
|
||||
normal zc
|
||||
198
|
||||
normal zc
|
||||
204
|
||||
normal zo
|
||||
241
|
||||
normal zc
|
||||
250
|
||||
normal zc
|
||||
269
|
||||
normal zc
|
||||
303
|
||||
normal zc
|
||||
312
|
||||
normal zo
|
||||
335
|
||||
normal zc
|
||||
423
|
||||
normal zc
|
||||
431
|
||||
normal zc
|
||||
455
|
||||
normal zc
|
||||
471
|
||||
normal zc
|
||||
492
|
||||
normal zc
|
||||
502
|
||||
normal zc
|
||||
508
|
||||
normal zc
|
||||
517
|
||||
normal zc
|
||||
528
|
||||
normal zc
|
||||
541
|
||||
normal zc
|
||||
554
|
||||
normal zc
|
||||
let s:l = 250 - ((25 * winheight(0) + 35) / 71)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
exe s:l
|
||||
normal! zt
|
||||
250
|
||||
normal! 0
|
||||
let &so = s:so_save | let &siso = s:siso_save
|
||||
doautoall SessionLoadPost
|
||||
" vim: set ft=vim :
|
||||
syntax on
|
@ -1,107 +0,0 @@
|
||||
let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
|
||||
argglobal
|
||||
edit ~/projects/jdb-labs/timestamper/web-app/src/ts_common.erl
|
||||
setlocal keymap=
|
||||
setlocal noarabic
|
||||
setlocal autoindent
|
||||
setlocal balloonexpr=
|
||||
setlocal nobinary
|
||||
setlocal bufhidden=
|
||||
setlocal buflisted
|
||||
setlocal buftype=
|
||||
setlocal nocindent
|
||||
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
|
||||
setlocal cinoptions=
|
||||
setlocal cinwords=if,else,while,do,for,switch
|
||||
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
|
||||
setlocal commentstring=/*%s*/
|
||||
setlocal complete=.,w,b,u,t,i
|
||||
setlocal completefunc=
|
||||
setlocal nocopyindent
|
||||
setlocal nocursorcolumn
|
||||
setlocal nocursorline
|
||||
setlocal define=
|
||||
setlocal dictionary=
|
||||
setlocal nodiff
|
||||
setlocal equalprg=
|
||||
setlocal errorformat=
|
||||
setlocal expandtab
|
||||
if &filetype != 'erlang'
|
||||
setlocal filetype=erlang
|
||||
endif
|
||||
setlocal foldcolumn=0
|
||||
setlocal foldenable
|
||||
setlocal foldexpr=0
|
||||
setlocal foldignore=#
|
||||
setlocal foldlevel=0
|
||||
setlocal foldmarker={{{,}}}
|
||||
setlocal foldmethod=manual
|
||||
setlocal foldminlines=1
|
||||
setlocal foldnestmax=20
|
||||
setlocal foldtext=foldtext()
|
||||
setlocal formatexpr=
|
||||
setlocal formatoptions=tcq
|
||||
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
|
||||
setlocal grepprg=
|
||||
setlocal iminsert=2
|
||||
setlocal imsearch=2
|
||||
setlocal include=
|
||||
setlocal includeexpr=
|
||||
setlocal indentexpr=
|
||||
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
|
||||
setlocal noinfercase
|
||||
setlocal iskeyword=@,48-57,_,192-255
|
||||
setlocal keywordprg=
|
||||
setlocal nolinebreak
|
||||
setlocal nolisp
|
||||
setlocal nolist
|
||||
setlocal makeprg=
|
||||
setlocal matchpairs=(:),{:},[:]
|
||||
setlocal nomodeline
|
||||
setlocal modifiable
|
||||
setlocal nrformats=octal,hex
|
||||
setlocal number
|
||||
setlocal numberwidth=4
|
||||
setlocal omnifunc=
|
||||
setlocal path=
|
||||
setlocal nopreserveindent
|
||||
setlocal nopreviewwindow
|
||||
setlocal quoteescape=\\
|
||||
setlocal noreadonly
|
||||
setlocal norightleft
|
||||
setlocal rightleftcmd=search
|
||||
setlocal noscrollbind
|
||||
setlocal shiftwidth=4
|
||||
setlocal noshortname
|
||||
setlocal nosmartindent
|
||||
setlocal softtabstop=0
|
||||
setlocal nospell
|
||||
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
|
||||
setlocal spellfile=
|
||||
setlocal spelllang=en
|
||||
setlocal statusline=
|
||||
setlocal suffixesadd=
|
||||
setlocal swapfile
|
||||
setlocal synmaxcol=3000
|
||||
if &syntax != 'erlang'
|
||||
setlocal syntax=erlang
|
||||
endif
|
||||
setlocal tabstop=4
|
||||
setlocal tags=
|
||||
setlocal textwidth=80
|
||||
setlocal thesaurus=
|
||||
setlocal nowinfixheight
|
||||
setlocal nowinfixwidth
|
||||
setlocal wrap
|
||||
setlocal wrapmargin=0
|
||||
silent! normal! zE
|
||||
let s:l = 16 - ((14 * winheight(0) + 23) / 46)
|
||||
if s:l < 1 | let s:l = 1 | endif
|
||||
exe s:l
|
||||
normal! zt
|
||||
16
|
||||
normal! 034l
|
||||
let &so = s:so_save | let &siso = s:siso_save
|
||||
doautoall SessionLoadPost
|
||||
" vim: set ft=vim :
|
||||
syntax on
|
86
web/Makefile
86
web/Makefile
@ -1,86 +0,0 @@
|
||||
MODS = $(wildcard src/*.erl)
|
||||
BEAMS = $(MODS:src/%.erl=build/ebin/%.beam)
|
||||
SCSS = $(wildcard www/css/*.scss)
|
||||
CSS_FILES = $(SCSS:www/css/%.scss=build/www/css/%.css)
|
||||
TEST_MODS = $(wildcard test/*.erl)
|
||||
TEST_BEAMS = $(TEST_MODS:test/%.erl=build/test/%.beam)
|
||||
TS_ROOT=/usr/local/var/yaws/timestamper.jdb-labs.com
|
||||
TS_ROOT_DEV=/home/jdbernard/temp/timestamper.jdb-labs.com
|
||||
BUILD_SERVER=dev.jdb-labs.com
|
||||
BUILD_SOURCE=/~jdbernard/projects/timestamper/web-app
|
||||
CWD = `pwd`
|
||||
|
||||
default: build
|
||||
|
||||
all : compile test
|
||||
|
||||
compile : init $(BEAMS) $(CSS_FILES)
|
||||
|
||||
compile-test : init $(TEST_BEAMS)
|
||||
|
||||
test : start-test-server run-test stop-test-server
|
||||
|
||||
test-shell : compile compile-test config-yaws-dev
|
||||
@echo Starting an interactive YAWS shell with test paths loaded.
|
||||
@yaws -i --pa build/ebin --pa build/test --id test_inst
|
||||
|
||||
run-test : compile compile-test config-yaws-dev
|
||||
@erl -pa ./build/ebin -pa ./build/test -run timestamper_api_tests test -run init stop -noshell
|
||||
|
||||
start-test-server :
|
||||
@yaws -D --id test_inst
|
||||
|
||||
stop-test-server :
|
||||
@yaws --stop --id test_inst
|
||||
|
||||
clean:
|
||||
rm -rf build*
|
||||
|
||||
init:
|
||||
-mkdir -p build/ebin
|
||||
-mkdir -p build/www/css
|
||||
-mkdir -p build/www/js
|
||||
-mkdir -p build/www/img
|
||||
|
||||
build/ebin/%.beam : src/%.erl
|
||||
erlc -W -o build/ebin $<
|
||||
|
||||
build/test/%.beam : test/%.erl
|
||||
@echo Compiling sources...
|
||||
erlc -W -o build/test $<
|
||||
|
||||
build/www/css/%.css : www/css/%.scss
|
||||
scss $< $@
|
||||
|
||||
build: compile
|
||||
-mkdir -p build/include
|
||||
cp -r www/js build/www/
|
||||
cp -r www/img build/www/
|
||||
cp -r www/*.* build/www/
|
||||
cp lib/* build/ebin
|
||||
cp src/ts_db_records.hrl build/include
|
||||
cp yaws.prod.conf build/yaws.conf
|
||||
tar czf timestamper-web.build.tar.gz build
|
||||
|
||||
deploy: build
|
||||
@service yaws stop
|
||||
@echo Removing existing artifacts.
|
||||
- @rm -r "$(TS_ROOT)"
|
||||
@echo Copying current artifacts.
|
||||
@cp -r build "$(TS_ROOT)"
|
||||
@service yaws start
|
||||
@echo Done.
|
||||
|
||||
deploy-dev: build
|
||||
@echo Removing existing artifacts.
|
||||
- rm -r $(TS_ROOT_DEV)
|
||||
@echo Copying current artifacts.
|
||||
cp -r build $(TS_ROOT_DEV)
|
||||
@echo Altering configuration for DEV.
|
||||
sed -i 's@$(TS_ROOT)@$(TS_ROOT_DEV)@' $(TS_ROOT_DEV)/yaws.conf
|
||||
# mv "$(TS_ROOT_DEV)/www" "$(TS_ROOT_DEV)/timestamper"
|
||||
# mkdir "$(TS_ROOT_DEV)/www"
|
||||
# mv "$(TS_ROOT_DEV)/timestamper" "$(TS_ROOT_DEV)/www/timestamper"
|
||||
@echo Done.
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
cXM
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,97 +0,0 @@
|
||||
TimeStamper Web Service API
|
||||
===========================
|
||||
|
||||
This document describes the REST API the TimeStamper web service exposes.
|
||||
|
||||
General Assumptions and Definitions
|
||||
-----------------------------------
|
||||
|
||||
Values that vary are notated using typeset in *emphasized text* when they
|
||||
appear in the text of the documentation and are ``<enclosed by angle
|
||||
brackets>`` when they appear in code examples or other monospaced text.
|
||||
|
||||
Paths in this document are relative to the server root. So
|
||||
``/<user-id>/current`` refers to ``http://sub.domain.tld/<user-id>/current``
|
||||
|
||||
Any paths inteded to be interpreted differently will use the full, absolute
|
||||
form (ie. ``http://www.twitter.com/bob``)
|
||||
|
||||
User Management: ``/<user-id>``
|
||||
-------------------------------
|
||||
|
||||
All requests directed at the user id URL are related to user management. The
|
||||
request is further interpreted by the request type. This resource responds to
|
||||
the ``GET``, ``POST``, ``PUT``, and ``DELETE`` HTTP verbs.
|
||||
|
||||
*user-id*:
|
||||
The user identifier, or username. This is a string value; valid characters
|
||||
are ``[a-zA-Z0-9_]``.
|
||||
|
||||
PUT
|
||||
~~~
|
||||
|
||||
Create a new user. A new user is created only if there is not an existing user
|
||||
with the same *user-id*. If
|
||||
|
||||
Returns:
|
||||
* ``201 Created`` if the user was successfully created.
|
||||
* ``409 Conflict`` if a user already exists for this *user-id*.
|
||||
|
||||
.. TODO: input format, preconditions, other returns
|
||||
|
||||
GET
|
||||
~~~
|
||||
|
||||
Returns the information about the user for *user-id*.
|
||||
|
||||
Return:
|
||||
* ``200 OK``
|
||||
* ``404 Not Found`` if there is no user for *user-id*.
|
||||
|
||||
POST
|
||||
~~~~
|
||||
|
||||
Updates information about the user for *user-id*.
|
||||
|
||||
DELETE
|
||||
~~~~~~
|
||||
|
||||
Deletes the user for *user-id*.
|
||||
|
||||
Timelines: ``/<user-id>/<timeline-id>``
|
||||
---------------------------------------
|
||||
|
||||
*user-id*:
|
||||
See `User Management`_.
|
||||
|
||||
*timeline-id*:
|
||||
A timeline identifier. A string; valid characters are ``[a-zA-Z0-9_]``.
|
||||
|
||||
GET
|
||||
~~~
|
||||
|
||||
Returns the timeline meta-data.
|
||||
|
||||
POST
|
||||
~~~~
|
||||
|
||||
Update timeline meta-data.
|
||||
|
||||
PUT
|
||||
~~~
|
||||
|
||||
Create a new timeline.
|
||||
|
||||
DELETE
|
||||
~~~~~~
|
||||
|
||||
Delete a timeline.
|
||||
|
||||
List Events
|
||||
-----------
|
||||
|
||||
``/<user-id>/<timeline-id>/list``
|
||||
---------------------------------
|
||||
|
||||
GET
|
||||
~~~
|
@ -1,24 +0,0 @@
|
||||
TimeStamper DB Layer
|
||||
====================
|
||||
|
||||
The following modules make up the database layer:
|
||||
|
||||
* ``ts_user``: Interface to user data.
|
||||
* ``ts_timeline``: Interface to timeline data.
|
||||
* ``ts_entry``: Interface to timeline entry data.
|
||||
* ``ts_ext_data``: Interface to extended data that can be set on different
|
||||
records.
|
||||
* ``ts_db_records``: Definition of data records.
|
||||
|
||||
The following modules and files are implementation details of the DB layer:
|
||||
|
||||
* ``id_counter``: Adds support for unique, sequential ID generation.
|
||||
* ``ts_common``: Provides the implementation for any operations that are common
|
||||
between the different interfaces.
|
||||
|
||||
Philosophy
|
||||
----------
|
||||
|
||||
The database layer should abstract all database-specific code away from the
|
||||
caller. Users of the DB layer should not have to think about transactions,
|
||||
locking, etc.
|
@ -1,4 +0,0 @@
|
||||
- Switch to local storage if unable to reach the server, sync when server is
|
||||
available.
|
||||
- Provide full-text search on timestamp marks and notes. Use Lucene in a
|
||||
seperate process? Build our own Erlang indexing code?
|
@ -1,4 +0,0 @@
|
||||
Refactor models and views.
|
||||
==========================
|
||||
|
||||
Try to find the behavior that is common to mobile and desktop versions.
|
@ -1,2 +0,0 @@
|
||||
Add UI for note taking.
|
||||
=======================
|
@ -1,9 +0,0 @@
|
||||
Add Markdown converter for notes.
|
||||
=================================
|
||||
|
||||
Brief description.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: 2011-05-15
|
||||
========= ==========
|
@ -1,11 +0,0 @@
|
||||
Duration mis-set on new entries.
|
||||
================================
|
||||
|
||||
Fix the duration bug when adding new events. Need to set the nextModel for
|
||||
the previously 'current' timestamp and set the nextModel of the new timestamp
|
||||
to 'null'
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: 2011-05-15
|
||||
========= ==========
|
@ -1,15 +0,0 @@
|
||||
Generate day seperators.
|
||||
========================
|
||||
|
||||
When generating EventViews in the EventListView, we need to automatically
|
||||
create and insert day seperators (see prototype).
|
||||
|
||||
Resolution
|
||||
----------
|
||||
|
||||
Day separators are added to the timeline by EntryListView.render.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: 2011-05-17
|
||||
========= ==========
|
@ -1,10 +0,0 @@
|
||||
Fix UI for tasks with a duration a day or longer.
|
||||
=================================================
|
||||
|
||||
Tasks that are extremely long-running can overflow the space set aside for
|
||||
the *Duration* column.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: YYYY-MM-DD
|
||||
========= ==========
|
@ -1,14 +0,0 @@
|
||||
Fix user menu UI.
|
||||
=================
|
||||
|
||||
UI for user menu does not work.
|
||||
|
||||
Resolution
|
||||
----------
|
||||
|
||||
UI menu changed to be displayed to the right of the username, in the empty black space vailable.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: 2011-06-01
|
||||
========= ==========
|
@ -1,9 +0,0 @@
|
||||
Fix timeline menu UI.
|
||||
=====================
|
||||
|
||||
UI for timeline menu does not work.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: 2011-06-07
|
||||
========= ==========
|
@ -1,9 +0,0 @@
|
||||
Implement timeline selection.
|
||||
=============================
|
||||
|
||||
Allow the user to change timelines.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: 2011-06-08
|
||||
========= ==========
|
@ -1,15 +0,0 @@
|
||||
Create UI for timeline creation.
|
||||
================================
|
||||
|
||||
There should be a way through the interface for a user to create a new
|
||||
timeline.
|
||||
|
||||
Resolution
|
||||
----------
|
||||
|
||||
Abstracted the login dialog CSS to support other dialogs of the same look and feel. Used this to create a dialog for creating new timelines.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: 2011-06-01
|
||||
========= ==========
|
@ -1,12 +0,0 @@
|
||||
Implement correct start time editor.
|
||||
====================================
|
||||
|
||||
The start time input field needs to look the same as the start time view.
|
||||
Alternatively, use a date picker.
|
||||
|
||||
----
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: YYYY-MM-DD
|
||||
========= ==========
|
@ -1,18 +0,0 @@
|
||||
Implement UI for entry re-order when chronological order changes.
|
||||
=================================================================
|
||||
|
||||
The UI should re-order the EntryList display when the chronological of the entries
|
||||
changes based on user input.
|
||||
|
||||
Two ways to do this spring to mind:
|
||||
|
||||
1. ``slideUp`` the EntryView at it's original position, find the new position,
|
||||
move it in the DOM and ``slideDown`` the element into view.
|
||||
2. Detach the element from the list, position it absolutely, animate it to it's new
|
||||
absolute position (based on the position of its new neighbors) and re-insert it
|
||||
into the list.
|
||||
|
||||
========= ==========
|
||||
Created: 2011-05-15
|
||||
Resolved: YYYY-MM-DD
|
||||
========= ==========
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user