diff --git a/.hgignore b/.hgignore index bdc021c..390fc33 100755 --- a/.hgignore +++ b/.hgignore @@ -1,3 +1,4 @@ .*.swp .*.swo staging +dist diff --git a/CHANGE ME b/CHANGE ME new file mode 100644 index 0000000..325d8bd Binary files /dev/null and b/CHANGE ME differ diff --git a/doc/uml.tsm b/doc/uml.tsm new file mode 100644 index 0000000..a725ebd Binary files /dev/null and b/doc/uml.tsm differ diff --git a/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy b/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy index 7cd5c83..149654b 100644 --- a/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy +++ b/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy @@ -8,8 +8,12 @@ class TimeStamperMainController { def model def view + def syncTimers = [:] + void mvcGroupInit(Map args) { + def configFile + logger.traceIfEnabled("Initializing TimeStamperMain MVC...") def thisMVC = ['model': model, 'view': view, 'controller': this] @@ -23,13 +27,13 @@ class TimeStamperMainController { // load application properties Properties prop = new Properties() String userHomeDir = System.getProperty('user.home') - model.configFile = new File(userHomeDir, ".timestamperrc") - if (!model.configFile.exists()) model.configFile.createNewFile() + configFile = new File(userHomeDir, ".timestamperrc") + if (!configFile.exists()) configFile.createNewFile() logger.traceIfEnabled("Reading configuration from " - + "'${model.configFile.name}'") + + "'${configFile.name}'") - try { model.configFile.withInputStream { prop.load(it) } } + try { configFile.withInputStream { prop.load(it) } } catch (IOException ioe) { logger.error('Unable to load configuration', ioe) } @@ -45,8 +49,9 @@ class TimeStamperMainController { logger.traceIfEnabled("Reading Timeline properties from '${lastUsed}'") - File propertyFile = new File(lastUsed) - if (!propertyFile.exists()) propertyFile.createNewFile() + model.timelinePropertiesFile = new File(lastUsed) + if (!model.timelinePropertiesFile.exists()) + model.timelinePropertiesFile.createNewFile() load(propertyFile) } diff --git a/griffon-app/models/com/jdbernard/timestamper/TimeStamperMainModel.groovy b/griffon-app/models/com/jdbernard/timestamper/TimeStamperMainModel.groovy index 68eb731..b3a2f84 100644 --- a/griffon-app/models/com/jdbernard/timestamper/TimeStamperMainModel.groovy +++ b/griffon-app/models/com/jdbernard/timestamper/TimeStamperMainModel.groovy @@ -13,7 +13,7 @@ class TimeStamperMainModel { @Bindable Timeline timeline @Bindable TimelineProperties timelineProperties Properties config - File configFile + File timelinePropertiesFile def notesDialogMVC def punchcardDialogMVC diff --git a/src/main/com/jdbernard/timestamper/core/SyncTarget.java b/src/main/com/jdbernard/timestamper/core/SyncTarget.java index 5489545..b7b07c8 100755 --- a/src/main/com/jdbernard/timestamper/core/SyncTarget.java +++ b/src/main/com/jdbernard/timestamper/core/SyncTarget.java @@ -6,8 +6,8 @@ import java.util.Timer; import java.util.TimerTask; /** - * - * @author Jonathan Bernard ({@literal jonathan.bernard@gemalto.com}) + * A remote target synchronized against the local timeline. + * @author Jonathan Bernard (jonathan.bernard@gemalto.com) */ public class SyncTarget { @@ -23,6 +23,9 @@ public class SyncTarget { protected boolean pullEnabled = true; protected boolean syncOnExit = true; + /** + * + */ protected class SyncTask extends TimerTask { @Override public void run() { synchronized(this) { @@ -109,21 +112,21 @@ public class SyncTarget { public long getSyncInterval() { return syncInterval; } - public synchronized void enablePush(boolean enablePush) { - this.pullEnabled = enablePush; + public synchronized void setPushEnabled(boolean pushEnabled) { + this.pullEnabled = pushEnabled; } - public boolean isPushEnabled() { return pushEnabled; } + public boolean getPushEnabled() { return pushEnabled; } - public synchronized void enablePull(boolean enablePull) { - this.pullEnabled = enablePull; + public synchronized void setPullEnabled(boolean pullEnabled) { + this.pullEnabled = pullEnabled; } - public boolean isPullEnabled() { return pullEnabled; } + public boolean getPullEnabled() { return pullEnabled; } - public synchronized void enableSyncOnExit(boolean syncOnExit) { + public synchronized void setSyncOnExit(boolean syncOnExit) { this.syncOnExit = syncOnExit; } - public boolean isSyncOnExitEnabled() { return syncOnExit; } + public boolean getSyncOnExit() { return syncOnExit; } } diff --git a/src/main/com/jdbernard/timestamper/core/Timeline.java b/src/main/com/jdbernard/timestamper/core/Timeline.java index 17c828f..4fd6c93 100755 --- a/src/main/com/jdbernard/timestamper/core/Timeline.java +++ b/src/main/com/jdbernard/timestamper/core/Timeline.java @@ -8,10 +8,11 @@ import java.util.Iterator; import java.util.TreeSet; /** - * @author Jonathan Bernard {@literal } * A Timeline object represents a series of markers at specific points in time. - * The markers have a name or symbol (the 'mark') and notes associated with that - * mark. + * It represents on logical timeline. The markers have a name or symbol (the + * 'mark') and notes associated with that mark. + * @author Jonathan Bernard {@literal } + * @see com.jdbernard.timestamper.core.TimelineSource */ public class Timeline implements Iterable { @@ -19,31 +20,39 @@ public class Timeline implements Iterable { public static SimpleDateFormat longFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy"); private TreeSet timelineList; + /** + * Create a new, empty Timeline. + */ public Timeline() { timelineList = new TreeSet(); } - public void addMarker(TimelineMarker tm) { timelineList.add(tm); } + /** + * Add a marker to the timeline. + * @param tm The TimelineMarker to add. + * @return true if this Timeline was modified. + */ + public boolean addMarker(TimelineMarker tm) { return timelineList.add(tm); } - public void addMarker(Date timestamp, String name, String notes) { - timelineList.add(new TimelineMarker(timestamp, name, notes)); - } - - public String getLastName(Date timestamp) { - TimelineMarker lastMarker = getLastMarker(timestamp); - return (lastMarker == null ? - "No previous marker." : - lastMarker.getMark()); - - } - - public String getLastNotes(Date timestamp) { - TimelineMarker lastMarker = getLastMarker(timestamp); - return (lastMarker == null ? - "No previous marker." : - lastMarker.getNotes()); + /** + * 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 true if this Timeline was modified. + */ + public boolean addMarker(Date timestamp, String name, String notes) { + return timelineList.add(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) { @@ -55,8 +64,13 @@ public class Timeline implements Iterable { return lastMarker; } - public void removeMarker(TimelineMarker marker) { - timelineList.remove(marker); + /** + * Remove a TimelineMarker from this Timelnie. + * @param marker The marker to remove. + * @return true if this Timeline was changed. + */ + public boolean removeMarker(TimelineMarker marker) { + return timelineList.remove(marker); } public Iterator iterator() { @@ -84,14 +98,31 @@ public class Timeline implements Iterable { return difference; } - public void addAll(Timeline t) { + /** + * Add all TimelineMarkers from t to this + * Timeline, excluding markers already present in this. + * @param t + * @return true if this Timeline was modified. + */ + public boolean addAll(Timeline t) { + boolean modified = false; for (TimelineMarker tm : t) { - if (!timelineList.contains(tm)) + if (!timelineList.contains(tm)) { timelineList.add(tm); + modified = true; + } } + + return modified; } - public void addAll(Collection c) { - timelineList.addAll(c); + /** + * Add all TimelineMarkers from c to this + * Timeline, excluding markers already present in this. + * @param c A Collection of TimelineMarkers + * @return true if this TImeline was modified. + */ + public boolean addAll(Collection c) { + return timelineList.addAll(c); } } diff --git a/src/main/com/jdbernard/timestamper/core/TimelineProperties.java b/src/main/com/jdbernard/timestamper/core/TimelineProperties.java index bcbd57d..f396394 100755 --- a/src/main/com/jdbernard/timestamper/core/TimelineProperties.java +++ b/src/main/com/jdbernard/timestamper/core/TimelineProperties.java @@ -4,7 +4,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; @@ -15,8 +17,32 @@ import java.util.regex.Pattern; /** - * + * 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. + *
+ * + * + * + * + * + * + * + * + * + * + *
PropertyDescription
timeline.uriThe URI of the primary (local) + * timeline
remote.timeline.name.uriThe URI for the name remote timeline.
remote.timeline.name.pushtrue to enable pushing updates to the name + * remote timeline, false to disable.
remote.timeline.name.pulltrue to enable pulling updates from the name + * remote timeline, false to disable.
remote.timeline.name.save-on-exit + * true to force sync the name remote + * timeline on exit.
remote.timeline.name + * .update-intervalThe time in milliseconds between + * synching the name remote timeline.
* @author Jonathan Bernard ({@literal jonathan.bernard@gemalto.com}) + * @see com.jdbernard.timestamper.core.Timeline + * @see com.jdbernard.timestamper.core.TimelineSource + * @see com.jdbernard.timestamper.core.SyncTarget */ public class TimelineProperties { @@ -26,13 +52,19 @@ public class TimelineProperties { private static final Pattern remoteTimelinePropPattern = Pattern.compile("\\Q" + REMOTE_TIMELINE_BASE + "\\E([^\\s\\.=]+?)[\\.=].*"); - private File propertyFile; private Timeline timeline; private TimelineSource timelineSource; private LinkedList syncTargets = new LinkedList(); + /** + * Create new TimelineProperties, using default values. This will create + * a new configuration using a FileTimelineSource pointed at + * 'timeline.default.txt' in the current directory and no + * remote Timelines. It will save this configuration to + * 'timeline.default.properties' in the current directory. + */ public TimelineProperties() { - this.propertyFile = new File("timeline.default.properties"); + File propertyFile = new File("timeline.default.properties"); Properties config = new Properties(); File timelineFile = new File("timeline.default.txt"); @@ -53,14 +85,17 @@ public class TimelineProperties { } } - public TimelineProperties(File propertyFile) throws IOException { + /** + * Load TimelineProperties from an InputStream. + * @param is + */ + public TimelineProperties(InputStream is) throws IOException { String strURI; URI timelineURI; - this.propertyFile = propertyFile; Properties config = new Properties(); try { - config.load(new InputStreamReader(new FileInputStream(propertyFile))); + config.load(new InputStreamReader(is)); } catch (IOException ioe) { // TODO } @@ -112,11 +147,11 @@ public class TimelineProperties { syncTargets.add(st); // check for synch options - st.enablePull(Boolean.parseBoolean( + st.setPullEnabled(Boolean.parseBoolean( config.getProperty(remoteBase + ".pull", "true"))); - st.enablePush(Boolean.parseBoolean( + st.setPushEnabled(Boolean.parseBoolean( config.getProperty(remoteBase + ".push", "true"))); - st.enableSyncOnExit(Boolean.parseBoolean( + st.setSyncOnExit(Boolean.parseBoolean( config.getProperty(remoteBase + ".sync-on-exit", "true"))); st.setSyncInterval(Long.parseLong( config.getProperty(remoteBase + ".update-interval", @@ -125,7 +160,7 @@ public class TimelineProperties { } } - public void save() throws IOException { + public void save(OutputStream os) throws IOException { Properties config = new Properties(); timelineSource.persist(timeline); @@ -137,27 +172,22 @@ public class TimelineProperties { config.setProperty(remoteBase + ".uri", st.getSource().getURI().toString()); config.setProperty(remoteBase + ".pull", - Boolean.toString(st.isPullEnabled())); + Boolean.toString(st.getPullEnabled())); config.setProperty(remoteBase + ".push", - Boolean.toString(st.isPushEnabled())); + Boolean.toString(st.getPushEnabled())); config.setProperty(remoteBase + ".sync-on-exit", - Boolean.toString(st.isSyncOnExitEnabled())); + Boolean.toString(st.getSyncOnExit())); config.setProperty(remoteBase + ".update-interval", Long.toString(st.getSyncInterval())); } try { - config.store(new FileOutputStream(propertyFile), ""); + config.store(os, ""); } catch (IOException ioe) { // TODO } } - public void save(File newFile) throws IOException { - propertyFile = newFile; - save(); - } - public Timeline getTimeline() { return timeline; } public void setTimelineSource(TimelineSource newSource) { diff --git a/src/main/com/jdbernard/timestamper/core/TimelineSource.java b/src/main/com/jdbernard/timestamper/core/TimelineSource.java index 136a632..de85445 100755 --- a/src/main/com/jdbernard/timestamper/core/TimelineSource.java +++ b/src/main/com/jdbernard/timestamper/core/TimelineSource.java @@ -4,8 +4,8 @@ import java.io.IOException; import java.net.URI; /** - * - * @author Jonathan Bernard ({@literal jonathan.bernard@gemalto.com}) + * A means of loading and persisting a Timeline. + * @author Jonathan Bernard (jonathan.bernard@gemalto.com) */ public abstract class TimelineSource { @@ -13,11 +13,36 @@ public abstract class TimelineSource { 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 true 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; }