Compare commits

..

No commits in common. "main" and "temp" have entirely different histories.
main ... temp

202 changed files with 1 additions and 20584 deletions

6
.gitignore vendored Normal file → Executable file
View File

@ -1,9 +1,5 @@
build/
release
dist/
.gradle/
staging/
temp/
release
.sass-cache
*.build.tar.gz
*.sw?

View File

@ -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')
}

View File

@ -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.

View File

@ -1,4 +0,0 @@
curdir="`pwd`"
cd ~/programs/timestamper-cli-@VERSION@
java -cp "lib:lib/*:./*" com.jdblabs.timestamper.cli.TimeStamperCLI -d "$curdir" "$@"
cd "$curdir"

View File

@ -1 +0,0 @@
rootProject.name = "timestamper-cli"

View File

@ -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`.
"""
}

View File

@ -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")
}
}

View File

@ -1,9 +0,0 @@
Add unit tests.
===============
----
========= ==========
Created: 2011-06-28
Resolved: YYYY-MM-DD
========= ==========

View File

@ -1 +0,0 @@
rootProject.name = "timestamper-lib"

View File

@ -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);
}
}

View File

@ -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() + ".");
}
}

View File

@ -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);
}
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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) {}
}
}

View File

@ -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;
}
}

View File

@ -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.");
}
}
}

View File

@ -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.");
}
}

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -1,5 +0,0 @@
#Mon, 03 Sep 2012 23:38:34 -0500
name=time-analyzer
version=1.0
build.number=11
lib.local=true

View File

@ -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 }

View File

@ -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})" }
}

View File

@ -1,7 +0,0 @@
package com.jdbernard.timeanalyzer.categories;
import com.jdbernard.timeanalyzer.events.Event;
public interface CategoryFilter {
public boolean matchesEvent(Event event);
}

View File

@ -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() }
}

View File

@ -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) } }
}

View File

@ -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 }
}

View File

@ -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) }
}

View File

@ -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) }
}

View File

@ -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) }
}

View File

@ -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)
}

View File

@ -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) } }
}

View File

@ -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 } }
}

View File

@ -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]}:.*/ } }
}

View File

@ -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 }
}

View File

@ -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})" }
}

View File

@ -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) }
}

View File

@ -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}.*/ }
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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";

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -1 +0,0 @@
cXM

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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
~~~

View File

@ -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.

View File

@ -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?

View File

@ -1,4 +0,0 @@
Refactor models and views.
==========================
Try to find the behavior that is common to mobile and desktop versions.

View File

@ -1,2 +0,0 @@
Add UI for note taking.
=======================

View File

@ -1,9 +0,0 @@
Add Markdown converter for notes.
=================================
Brief description.
========= ==========
Created: 2011-05-15
Resolved: 2011-05-15
========= ==========

View File

@ -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
========= ==========

View File

@ -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
========= ==========

View File

@ -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
========= ==========

View File

@ -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
========= ==========

View File

@ -1,9 +0,0 @@
Fix timeline menu UI.
=====================
UI for timeline menu does not work.
========= ==========
Created: 2011-05-15
Resolved: 2011-06-07
========= ==========

View File

@ -1,9 +0,0 @@
Implement timeline selection.
=============================
Allow the user to change timelines.
========= ==========
Created: 2011-05-15
Resolved: 2011-06-08
========= ==========

View File

@ -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
========= ==========

View File

@ -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
========= ==========

View File

@ -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