This is an automated email from the git hooks/post-receive script. New commit to branch timebundle in repository jtimer. See https://gitlab.nuiton.org/chorem/jtimer.git commit df7f10fe33e431c480a54d6eb1337a847a0de50c Author: Eric Chatellier <chatellier@codelutin.com> Date: Tue Aug 2 16:50:41 2016 +0200 Refactor time bundle code into dedicated plugin. --- src/main/java/org/chorem/jtimer/JTimer.java | 55 +-- src/main/java/org/chorem/jtimer/JTimerConfig.java | 21 +- src/main/java/org/chorem/jtimer/JTimerFactory.java | 41 +- .../org/chorem/jtimer/data/DataEventListener.java | 20 +- .../java/org/chorem/jtimer/data/TimerCore.java | 9 +- .../org/chorem/jtimer/data/TimerDataManager.java | 68 ++-- .../java/org/chorem/jtimer/entities/SyncInfo.java | 187 --------- .../java/org/chorem/jtimer/entities/TimerSync.java | 151 +++++++ .../java/org/chorem/jtimer/entities/TimerTask.java | 179 +------- .../chorem/jtimer/entities/TimerTaskHelper.java | 102 ----- .../org/chorem/jtimer/entities/package-info.java | 2 +- .../java/org/chorem/jtimer/io/AbstractSaver.java | 83 ---- .../io/{AbstractSaver.java => BackupUtils.java} | 55 +-- .../chorem/jtimer/io/GTimerIncrementalSaver.java | 140 +------ .../chorem/jtimer/io/TimerTaskSynchronizer.java | 299 -------------- .../jtimer/plugin/timebundle/TimeBundleHelper.java | 140 +++++++ .../jtimer/plugin/timebundle/TimeBundleSaver.java | 220 ++++++++++ .../plugin/timebundle/TimeBundleSynchronizer.java | 148 +++++++ .../plugin/timebundle/TimeBundleVetoable.java | 61 +++ .../plugin/timebundle/TimerSyncCellRenderer.java | 46 +++ .../jtimer/plugin/timebundle/TimerSyncEditor.java | 245 +++++++++++ .../plugin/timebundle/TimerSyncTableModel.java | 203 +++++++++ .../jtimer/ui/report/TimerTaskSyncInfoEditor.java | 453 --------------------- src/main/resources/log4j2.xml | 1 + .../resources/TimerSyncEditor.properties} | 4 +- .../resources/TimerSyncEditor_fr.properties} | 2 +- .../plugin/timebundle/resources/dialog-close.png | Bin 0 -> 1382 bytes .../org/chorem/jtimer/resources/JTimer.properties | 8 +- .../chorem/jtimer/resources/JTimer_fr.properties | 7 +- .../jtimer/ui/resources/TimerTaskEditor.properties | 2 +- .../ui/resources/TimerTaskEditor_fr.properties | 2 +- src/site/rst/timebundle.rst | 30 ++ src/site/site.xml | 5 +- .../java/org/chorem/jtimer/AbstractJTimerTest.java | 2 + .../jtimer/entities/TimerTaskHelperTest.java | 62 +-- .../org/chorem/jtimer/entities/TimerTaskTest.java | 42 -- .../org/chorem/jtimer/io/AbstractSaverTest.java | 12 +- .../jtimer/io/GTimerIncrementalSaverTest.java | 23 +- .../plugin/timebundle/TImeBundleSaverTest.java | 45 ++ .../timebundle/TimeBundleHelperTest.java} | 42 +- src/test/resources/testdata/41.task.sync | 8 - src/test/resources/testsync/41.task.sync | 8 + 42 files changed, 1477 insertions(+), 1756 deletions(-) diff --git a/src/main/java/org/chorem/jtimer/JTimer.java b/src/main/java/org/chorem/jtimer/JTimer.java index 3981915..48faf5e 100644 --- a/src/main/java/org/chorem/jtimer/JTimer.java +++ b/src/main/java/org/chorem/jtimer/JTimer.java @@ -59,6 +59,7 @@ import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -74,7 +75,7 @@ import org.chorem.jtimer.ui.StatusBar; import org.chorem.jtimer.ui.TimerTaskEditor; import org.chorem.jtimer.ui.alert.AlertEditor; import org.chorem.jtimer.ui.report.ReportView; -import org.chorem.jtimer.ui.report.TimerTaskSyncInfoEditor; +import org.chorem.jtimer.plugin.timebundle.TimerSyncEditor; import org.chorem.jtimer.ui.systray.SystrayManager; import org.chorem.jtimer.ui.tasks.IdleDialog; import org.chorem.jtimer.ui.tasks.RefreshTreeTask; @@ -366,13 +367,19 @@ public class JTimer extends SingleFrameApplication implements String[] projectMenuActionNames = { "newProject", "editProject", "closeProject", "deleteProject", "---", "quit"}; + if (JTimerFactory.getSynchronizer() != null) { + projectMenuActionNames = ArrayUtils.add(projectMenuActionNames, 2, "editSync"); + } menuBar.add(createMenu("projectMenu", projectMenuActionNames)); - String[] taskMenuActionNames = { "newTask", "editTask", "updateTask", "closeTask", + String[] taskMenuActionNames = { "newTask", "editTask", "closeTask", "deleteTask", "---", "startTask", "stopAllTasks", "---", "addAnnotation", "editAlert", "increment1Task", "increment5Task", "increment30Task", "decrement1Task", "decrement5Task", "decrement30Task", "setToZero", "mergeTasks" }; + if (JTimerFactory.getSynchronizer() != null) { + taskMenuActionNames = ArrayUtils.add(taskMenuActionNames, 2, "editSync"); + } menuBar.add(createMenu("taskMenu", taskMenuActionNames)); String[] reportMenuActionNames = { "makeReport" }; @@ -666,11 +673,11 @@ public class JTimer extends SingleFrameApplication implements * Update task. * Enabled when a task is selected */ - @Action(enabledProperty = "selectedSingleTask") - public void updateTask() { - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + @Action(enabledProperty = "selectedSingleElement") + public void editSync() { + TimerTask task = projectsAndTasksTable.getSelectedElements().get(0); - TimerTaskSyncInfoEditor updater = new TimerTaskSyncInfoEditor(this, core, task); + TimerSyncEditor updater = new TimerSyncEditor(this, core, task); show(updater); } @@ -1054,24 +1061,10 @@ public class JTimer extends SingleFrameApplication implements TimerTask destinationTask = tasks.get(0); List<TimerTask> otherTasks = tasks.subList(1, tasks.size()); - //check for syncInfos, if there are some in the tasks being merged, warn with a message - String mergeMessage = resourceMap.getString("input.mergeTaskMessage", tasks.size(), - destinationTask.getName()); - boolean syncInfoInMergedTasks = false; - for (TimerTask task : otherTasks) { - if (!task.getSynchronizingInfoList().isEmpty()) { - syncInfoInMergedTasks = true; - } - } - if (syncInfoInMergedTasks) { - mergeMessage = resourceMap.getString("input.mergeTaskWithSyncInfoMessage", tasks.size(), destinationTask.getName()); - } - int confirm = JOptionPane.showConfirmDialog(getMainFrame(), mergeMessage - , resourceMap - .getString("input.mergeTaskTitle"), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int confirm = JOptionPane.showConfirmDialog(getMainFrame(), + resourceMap.getString("input.mergeTaskMessage", tasks.size(), destinationTask.getName()), + resourceMap.getString("input.mergeTaskTitle"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (confirm == JOptionPane.YES_OPTION) { - try { core.getData().mergeTasks(destinationTask, otherTasks); } catch (DataViolationException e) { @@ -1436,9 +1429,7 @@ public class JTimer extends SingleFrameApplication implements systrayManager.postIdleDetect(); } - /* - * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) - */ + @Override public void mouseClicked(MouseEvent e) { if (log.isDebugEnabled()) { @@ -1496,14 +1487,20 @@ public class JTimer extends SingleFrameApplication implements if (isSelectedSingleProject()) { actionNames = new String[] { "newTask", "---", "newProject", "editProject", "closeProject", "deleteProject" }; + if (JTimerFactory.getSynchronizer() != null) { + actionNames = ArrayUtils.add(actionNames, 4, "editSync"); + } } if (isSelectedSingleTask()) { actionNames = new String[] { "startTask", "---", - "newTask", "editTask", "updateTask", "closeTask", "deleteTask", + "newTask", "editTask", "closeTask", "deleteTask", "---", "addAnnotation", "editAlert", "increment1Task", "increment5Task", "increment30Task", "decrement1Task", "decrement5Task", "decrement30Task", "setToZero" }; + if (JTimerFactory.getSynchronizer() != null) { + actionNames = ArrayUtils.add(actionNames, 4, "editSync"); + } } if (isSelectedMultiplesTasks()) { @@ -1519,15 +1516,19 @@ public class JTimer extends SingleFrameApplication implements } } + @Override public void mouseEntered(MouseEvent e) { } + @Override public void mouseExited(MouseEvent e) { } + @Override public void mousePressed(MouseEvent e) { } + @Override public void mouseReleased(MouseEvent e) { } } diff --git a/src/main/java/org/chorem/jtimer/JTimerConfig.java b/src/main/java/org/chorem/jtimer/JTimerConfig.java index 758cb43..778b5cd 100644 --- a/src/main/java/org/chorem/jtimer/JTimerConfig.java +++ b/src/main/java/org/chorem/jtimer/JTimerConfig.java @@ -120,6 +120,16 @@ public class JTimerConfig { /** * Get jtimer data directory. + * + * @since 1.5.1 + * @return jtimer 1.5.1 sync directory + */ + public File getHomeDirectory() { + return appConfig.getOptionAsFile(JTimerOption.HOME_DIRECTORY.key); + } + + /** + * Get jtimer data directory. * * @since 1.5 * @return jtimer 1.5 data directory @@ -176,14 +186,6 @@ public class JTimerConfig { } /** - * Returns timezone for sync - * @return timezone - */ - public String getIOSyncTimeZone() { - return appConfig.getOption(JTimerOption.IO_SYNC_TIMEZONE.key); - } - - /** * Return user idle time threshold in seconds. * * @return idle time threshold @@ -295,8 +297,7 @@ public class JTimerConfig { GTIMER_BACKUP_DIRECTORY("jtimer.io.backup.directory", "${jtimer.io.saver.directory}/backups"), IO_SAVER_AUTOSAVEDELAY("jtimer.io.saver.autosavedelay", "300"), - IO_SYNC_CLASS("jtimer.io.synchronizer.class", "org.chorem.jtimer.io.TimerTaskSynchronizer"), - IO_SYNC_TIMEZONE("jtimer.io.synchronizer.timezone", "+01:00"), + IO_SYNC_CLASS("jtimer.io.synchronizer.class", null), UI_IDLE_TIME("jtimer.ui.idletime", "300"), UI_SHOW_CLOSED("jtimer.ui.showclosed", "false"), diff --git a/src/main/java/org/chorem/jtimer/JTimerFactory.java b/src/main/java/org/chorem/jtimer/JTimerFactory.java index de4aef4..5689b49 100644 --- a/src/main/java/org/chorem/jtimer/JTimerFactory.java +++ b/src/main/java/org/chorem/jtimer/JTimerFactory.java @@ -25,7 +25,7 @@ package org.chorem.jtimer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.chorem.jtimer.io.Saver; -import org.chorem.jtimer.io.TimerTaskSynchronizer; +import org.chorem.jtimer.plugin.timebundle.TimeBundleSynchronizer; /** * JTimer config class. @@ -45,7 +45,7 @@ public class JTimerFactory { protected static Saver saver; /** Synchronizer */ - protected static TimerTaskSynchronizer synchronizer; + protected static TimeBundleSynchronizer synchronizer; /** * Constructeur. @@ -96,30 +96,29 @@ public class JTimerFactory { /** * Get synchronizer manager */ - public static TimerTaskSynchronizer getSynchronizer(){ + public static TimeBundleSynchronizer getSynchronizer(){ if (synchronizer == null) { Class<?> synchronizerClass = JTimer.config.getIOSynchronizerClass(); - // log - if (log.isInfoEnabled()) { - log.info("Using synchronizer class : " + synchronizerClass); - } - - try { - // get instance - synchronizer = (TimerTaskSynchronizer) synchronizerClass.newInstance(); - - //set timezone to synchronizer - synchronizer.setTimezone(JTimer.config.getIOSyncTimeZone()); - - } catch (InstantiationException e) { - if (log.isErrorEnabled()) { - log.error("Can't instanciate class : " + synchronizerClass, e); + if (synchronizerClass != null) { + // log + if (log.isInfoEnabled()) { + log.info("Using synchronizer class : " + synchronizerClass); } - } catch (IllegalAccessException e) { - if (log.isErrorEnabled()) { - log.error("Can't access class : " + synchronizerClass, e); + + try { + // get instance + synchronizer = (TimeBundleSynchronizer) synchronizerClass.newInstance(); + + } catch (InstantiationException e) { + if (log.isErrorEnabled()) { + log.error("Can't instanciate class : " + synchronizerClass, e); + } + } catch (IllegalAccessException e) { + if (log.isErrorEnabled()) { + log.error("Can't access class : " + synchronizerClass, e); + } } } } diff --git a/src/main/java/org/chorem/jtimer/data/DataEventListener.java b/src/main/java/org/chorem/jtimer/data/DataEventListener.java index 8d20530..f26e548 100644 --- a/src/main/java/org/chorem/jtimer/data/DataEventListener.java +++ b/src/main/java/org/chorem/jtimer/data/DataEventListener.java @@ -27,7 +27,7 @@ import java.util.Date; import java.util.EventListener; import java.util.List; -import org.chorem.jtimer.entities.SyncInfo; +import org.chorem.jtimer.entities.TimerSync; import org.chorem.jtimer.entities.TimerProject; import org.chorem.jtimer.entities.TimerTask; @@ -190,23 +190,5 @@ public interface DataEventListener extends EventListener { default void dataLoaded(Collection<TimerProject> projects) { } - - /** - * SyncInfo changed - * @param task TimerTask - * @param syncInfo SyncInfo - */ - default void syncInfoChanged(TimerTask task, SyncInfo syncInfo) { - - } - - /** - * SyncInfo deleted - * @param task TimerTask - * @param syncInfo SyncInfo - */ - default void syncInfoDeleted(TimerTask task, SyncInfo syncInfo) { - - } } diff --git a/src/main/java/org/chorem/jtimer/data/TimerCore.java b/src/main/java/org/chorem/jtimer/data/TimerCore.java index c6be19f..44fec8a 100644 --- a/src/main/java/org/chorem/jtimer/data/TimerCore.java +++ b/src/main/java/org/chorem/jtimer/data/TimerCore.java @@ -36,7 +36,9 @@ import org.chorem.jtimer.JTimerFactory; import org.chorem.jtimer.entities.TimerProject; import org.chorem.jtimer.io.DataLockingException; import org.chorem.jtimer.io.Saver; -import org.chorem.jtimer.io.TimerTaskSynchronizer; +import org.chorem.jtimer.plugin.timebundle.TimeBundleSaver; +import org.chorem.jtimer.plugin.timebundle.TimeBundleSynchronizer; +import org.chorem.jtimer.plugin.timebundle.TimeBundleVetoable; /** * TimerCore @@ -59,7 +61,7 @@ public class TimerCore { protected Saver saver; /** sync */ - protected TimerTaskSynchronizer synchronizer; + protected TimeBundleSynchronizer synchronizer; /** * Constructor. @@ -92,9 +94,10 @@ public class TimerCore { //init sync synchronizer = JTimerFactory.getSynchronizer(); if (synchronizer != null) { + data.addVetoableDataEventListener(new TimeBundleVetoable()); + data.addDataEventListener(new TimeBundleSaver()); data.addDataEventListener(synchronizer); } - } /** diff --git a/src/main/java/org/chorem/jtimer/data/TimerDataManager.java b/src/main/java/org/chorem/jtimer/data/TimerDataManager.java index 6d03164..b8bd106 100644 --- a/src/main/java/org/chorem/jtimer/data/TimerDataManager.java +++ b/src/main/java/org/chorem/jtimer/data/TimerDataManager.java @@ -36,7 +36,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.chorem.jtimer.JTimer; -import org.chorem.jtimer.entities.SyncInfo; +import org.chorem.jtimer.entities.TimerSync; import org.chorem.jtimer.entities.TimerProject; import org.chorem.jtimer.entities.TimerTask; @@ -366,7 +366,10 @@ public class TimerDataManager { * * @param project project to edit * @param newProjectName new project name + * + * @deprecated since 1.5.1 this is duplicated with modifyProject */ + @Deprecated public void editProject(TimerProject project, String newProjectName) { @@ -390,7 +393,10 @@ public class TimerDataManager { * * @param task task to edit * @param newTaskName new task name + * + * @deprecated since 1.5.1 this is duplicated with modifyTask */ + @Deprecated public void editTask(TimerTask task, String newTaskName) { // fire vetoable event @@ -410,6 +416,32 @@ public class TimerDataManager { } /** + * Modify project. + * + * @param project project to edit + */ + public void modifyProject(TimerProject project) { + + // send notification + for (DataEventListener dataEventListener : dataEventListeners) { + dataEventListener.modifyProject(project); + } + } + + /** + * Modify task. + * + * @param task task to edit + */ + public void modifyTask(TimerTask task) { + + // send notification + for (DataEventListener dataEventListener : dataEventListeners) { + dataEventListener.modifyTask(task); + } + } + + /** * Move task. * * @param destination task to move to @@ -636,38 +668,4 @@ public class TimerDataManager { return foundTask; } - - /** - * Edit Synchronization Information - * @param task : the task to edit - * @param info : the SyncInfo that was added - */ - public void syncInfoChanged(TimerTask task, SyncInfo info) { - - task.addSyncInfo(info); - - for (DataEventListener dataEventListener : dataEventListeners) { - dataEventListener.syncInfoChanged(task, info); - } - if (log.isDebugEnabled()) { - log.debug("SyncInfo changed: " + info.getSyncURL()); - } - } - - /** - * When a syncInfo is deleted - * @param task : the task to edit - * @param info : the SyncInfo to remove - */ - public void deleteSyncInfo(TimerTask task, SyncInfo info) { - if (task.getSynchronizingInfoList().contains(info)) { - task.removeSyncInfo(info); - } - for (DataEventListener dataEventListener : dataEventListeners) { - dataEventListener.syncInfoDeleted(task, info); - } - if (log.isDebugEnabled()) { - log.debug("SyncInfo deleted: " + info.getSyncURL()); - } - } } diff --git a/src/main/java/org/chorem/jtimer/entities/SyncInfo.java b/src/main/java/org/chorem/jtimer/entities/SyncInfo.java deleted file mode 100644 index f31f121..0000000 --- a/src/main/java/org/chorem/jtimer/entities/SyncInfo.java +++ /dev/null @@ -1,187 +0,0 @@ -/*- - * #%L - * jTimer - * %% - * Copyright (C) 2016 CodeLutin, Charlène Servantie - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ - -package org.chorem.jtimer.entities; - -import java.util.Date; - -/** - * Class to structure synchronization info - * Created by servantie on 13/06/16. - */ -public class SyncInfo { - - /** - * the url to synchronize on - */ - protected String syncURL; - - /** - * the last time the synchronization happened successfully - */ - protected Date lastSync; - - /** - * true if the synchronization is active - */ - protected boolean isActiveSync; - - /** - * true if annotations included - */ - protected boolean isWithAnnotations; - - /** - * constructor with all parameters - * - * @param syncURL the url to sync to - * @param lastSync the last time it was synced successfully - * @param isActiveSync if true, will sync auto - * @param isWithAnnotations if true, will add annotations - */ - public SyncInfo(String syncURL, Date lastSync, boolean isActiveSync, boolean isWithAnnotations) { - this.syncURL = syncURL; - this.lastSync = lastSync; - this.isActiveSync = isActiveSync; - this.isWithAnnotations = isWithAnnotations; - } - - /** - * Constructor with just the url, at creation the sync is considered active by default, - * and annotations are not sent by default - * - * @param syncURL the url to sync to - */ - public SyncInfo(String syncURL) { - this.syncURL = syncURL; - this.isActiveSync = true; - this.isWithAnnotations = false; - this.lastSync = new Date(0); - } - - /** - * Returns the time of the last sync - * - * @return LocalDateTime - */ - public Date getLastSync() { - return lastSync; - } - - /** - * Returns a boolean if sync should send to this url - * - * @return aboolean - */ - public boolean isActiveSync() { - return isActiveSync; - } - - /** - * Sets the setActiveSync boolean - * - * @param isActive boolean - */ - public void setActiveSync(boolean isActive) { - isActiveSync = isActive; - } - - /** - * Returns a boolean for the inclusion of annotations - * in the sync - * - * @return a boolean - */ - public boolean isWithAnnotations() { - return isWithAnnotations; - } - - /** - * Sets the isWithAnnotations boolean - * - * @param hasAnnotations boolean - */ - public void setWithAnnotations(boolean hasAnnotations) { - isWithAnnotations = hasAnnotations; - } - - /** - * Set the syncURL - * - * @param syncURL String - */ - public void setSyncURL(String syncURL) { - this.syncURL = syncURL; - } - - /** - * Get the syncURL - * - * @return String - */ - public String getSyncURL() { - return syncURL; - } - - /** - * Set Sync time - * - * @param syncTime Date - */ - public void setLastSync(Date syncTime) { - lastSync = syncTime; - } - - /** - * Overrides the toString() method to return the url (for combobox) - */ - @Override - public String toString() { - return syncURL; - } - - /** - * Override the equals - */ - @Override - public boolean equals(Object o) { - if (o instanceof SyncInfo) { - SyncInfo objInfo = (SyncInfo) o; - if (objInfo.getSyncURL() != null) { - if (objInfo.getSyncURL().equals(this.getSyncURL())) { - return true; - } - } - } - return false; - } - - /** - * Overriding hashcode - */ - @Override - public int hashCode() { - int hash = syncURL.hashCode(); - return hash; - } -} - diff --git a/src/main/java/org/chorem/jtimer/entities/TimerSync.java b/src/main/java/org/chorem/jtimer/entities/TimerSync.java new file mode 100644 index 0000000..dfd15a9 --- /dev/null +++ b/src/main/java/org/chorem/jtimer/entities/TimerSync.java @@ -0,0 +1,151 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer.entities; + +import java.util.Date; + +/** + * Class to structure synchronization info. + */ +public class TimerSync { + + /** + * the url to synchronize on + */ + protected String url; + + /** + * the last time the synchronization happened successfully + */ + protected Date lastSync; + + /** + * true if the synchronization is active + */ + protected boolean active; + + /** + * true if annotations included + */ + protected boolean withAnnotations; + + /** + * constructor with all parameters + * + * @param url the url to sync to + * @param lastSync the last time it was synced successfully + * @param active if true, will sync auto + * @param withAnnotations if true, will add annotations + */ + public TimerSync(String url, Date lastSync, boolean active, boolean withAnnotations) { + this.url = url; + this.lastSync = lastSync; + this.active = active; + this.withAnnotations = withAnnotations; + } + + /** + * Constructor with just the url, at creation the sync is considered active by default, + * and annotations are not sent by default + * + * @param url the url to sync to + */ + public TimerSync(String url) { + this(url, new Date(0), true, false); + } + + /** + * Returns the time of the last sync + * + * @return LocalDateTime + */ + public Date getLastSync() { + return lastSync; + } + + /** + * Returns a boolean if sync should send to this url + * + * @return a boolean + */ + public boolean isActive() { + return active; + } + + /** + * Sets the setActive boolean + * + * @param active boolean + */ + public void setActive(boolean active) { + this.active = active; + } + + /** + * Returns a boolean for the inclusion of annotations + * in the sync + * + * @return a boolean + */ + public boolean isWithAnnotations() { + return withAnnotations; + } + + /** + * Sets the withAnnotations boolean + * + * @param withAnnotations boolean + */ + public void setWithAnnotations(boolean withAnnotations) { + this.withAnnotations = withAnnotations; + } + + /** + * Set the url + * + * @param url String + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * Get the url + * + * @return String + */ + public String getUrl() { + return url; + } + + /** + * Set Sync time + * + * @param syncTime Date + */ + public void setLastSync(Date syncTime) { + lastSync = syncTime; + } + +} + diff --git a/src/main/java/org/chorem/jtimer/entities/TimerTask.java b/src/main/java/org/chorem/jtimer/entities/TimerTask.java index f59ddd7..0043d52 100644 --- a/src/main/java/org/chorem/jtimer/entities/TimerTask.java +++ b/src/main/java/org/chorem/jtimer/entities/TimerTask.java @@ -91,7 +91,7 @@ public class TimerTask implements Cloneable, /** * Synchronization Info */ - protected List<SyncInfo> synchronisingInfoList; + protected List<TimerSync> syncs; /** * Constructor. @@ -102,7 +102,7 @@ public class TimerTask implements Cloneable, allDaysAnnotations = new TreeMap<>(); subTasks = new ArrayList<>(); alerts = new ArrayList<>(); - synchronisingInfoList = new ArrayList<>(); + syncs = new ArrayList<>(); // wrong value to detect bug number = -1; } @@ -224,168 +224,6 @@ public class TimerTask implements Cloneable, } /** - * Get task's last sync time associated to the url - * If there is no last sync, will return a very far past time - * - * @param urlString the url to get the last sync from - * @return the date of the sync - */ - public Date getLastSync(String urlString) { - //to avoid null, a default time to return if errors - Date resultTime = new Date(0); - if (!synchronisingInfoList.isEmpty()) { - for (SyncInfo sync : synchronisingInfoList) { - if (urlString.equals(sync.getSyncURL())) { - resultTime = sync.lastSync; - } - } - } - return resultTime; - } - - /** - * Sets task's last sync time on one url - * (if the url doesn't exist in the task's sync info, adds it) - * - * @param syncDate : date of the sync - * @param syncURL : the url that has a change of syncdate - */ - public void setLastSync(Date syncDate, String syncURL) { - boolean urlinList = false; - if (!synchronisingInfoList.isEmpty()) { - for (SyncInfo sync : synchronisingInfoList) { - if (sync.syncURL.equals(syncURL)) { - sync.setLastSync(syncDate); - urlinList = true; - } - } - } - if (!urlinList) { - synchronisingInfoList.add(new SyncInfo(syncURL)); - } - } - - /** - * Get task's sync URL List - * returns an empty list if there is no info - * - * @return the URL List - */ - public List<String> getSynchronizingURLList() { - List<String> resultList = new ArrayList<>(); - if (!synchronisingInfoList.isEmpty()) { - for (SyncInfo sync : synchronisingInfoList) { - resultList.add(sync.getSyncURL()); - } - } - return resultList; - } - - /** - * Returns only the URLs that are active - * - * @return a List<String> with only active urls - */ - public List<String> getActiveSynchronizingURLList() { - List<String> resultList = new ArrayList<>(); - if (!synchronisingInfoList.isEmpty()) { - for (SyncInfo sync : synchronisingInfoList) { - if (sync.isActiveSync) { - resultList.add(sync.getSyncURL()); - } - } - } - return resultList; - } - - /** - * Adds a new synchronization info if the url isn't already present in the list - * sync activity true by default, annotations false by default - * - * @param url a string - * @param time a LocalDateTime - */ - public void addSyncInfo(String url, Date time) { - if (!url.isEmpty() && !getSynchronizingURLList().contains(url)) { - synchronisingInfoList.add(new SyncInfo(url, time, true, false)); - } - } - - /** - * Adds a new synchronization info with just url provided - * (default isActive = true and isWithAnnotations false) - * - * @param url a String - */ - public void addSyncInfo(String url) { - if (!url.isEmpty()) { - addSyncInfo(url, new Date(0)); - } - } - - /** - * Adds a SyncInfo to the task - * - * @param info a SyncInfo - */ - - public void addSyncInfo(SyncInfo info) { - if (!getSynchronizingInfoList().contains(info) && !info.getSyncURL().isEmpty()) { - synchronisingInfoList.add(info); - } - } - - - /** - * Removes a synchronization info attached to a url if it exists - * - * @param info : the sync info to remove - */ - public void removeSyncInfo(SyncInfo info) { - synchronisingInfoList.remove(info); - } - - /** - * Returns all the synchronization info as a List, removes the empty ones first if they exist - * - * @return a list of TimerTask.SyncInfo - */ - public List<SyncInfo> getSynchronizingInfoList() { - removeSyncInfo(new SyncInfo("")); - return synchronisingInfoList; - } - - - /** - * Returns the sync info matching a url - * (if the url is not in the list, returns a new SyncInfo with the url and adds it to the list) - * - * @param urlString - * @return a SyncInfo matching the url - */ - public SyncInfo getSynchronizingInfo(String urlString) { - for (SyncInfo sync : synchronisingInfoList) { - if (sync.getSyncURL().equals(urlString)) { - return sync; - } - } - SyncInfo newInfo = new SyncInfo(urlString); - synchronisingInfoList.add(newInfo); - return newInfo; - - } - - /** - * sets the activity of a sync info - * - * @param isActive a boolean - * @param syncInfo the SyncInfo - */ - public void setIsActive(boolean isActive, SyncInfo syncInfo) { - syncInfo.setActiveSync(isActive); - } - - /** * Add task's subtask. * * Also add parent reference. @@ -483,6 +321,18 @@ public class TimerTask implements Cloneable, this.alerts = alerts; } + public void addSync(TimerSync sync) { + syncs.add(sync); + } + + public List<TimerSync> getSyncs() { + return syncs; + } + + public void setSyncs(List<TimerSync> syncs) { + this.syncs = syncs; + } + @Override public String toString() { return name + subTasks.toString(); @@ -525,6 +375,7 @@ public class TimerTask implements Cloneable, task.allDaysTimes = new DailySortedMap<>(allDaysTimes); task.allDaysAnnotations = new TreeMap<>(allDaysAnnotations); task.subTasks = new ArrayList<>(subTasks); + task.syncs = new ArrayList<>(syncs); } catch (CloneNotSupportedException e) { throw new RuntimeException("Can't clone", e); } diff --git a/src/main/java/org/chorem/jtimer/entities/TimerTaskHelper.java b/src/main/java/org/chorem/jtimer/entities/TimerTaskHelper.java index a3343b0..e6f8eb2 100644 --- a/src/main/java/org/chorem/jtimer/entities/TimerTaskHelper.java +++ b/src/main/java/org/chorem/jtimer/entities/TimerTaskHelper.java @@ -22,10 +22,6 @@ package org.chorem.jtimer.entities; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import java.time.LocalDate; -import java.time.ZoneId; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -37,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; -import org.apache.commons.lang3.time.DateUtils; /** * Helper to remove process code from entity @@ -334,101 +329,4 @@ public class TimerTaskHelper { return components; } - - /** - * makes a JSONObject corresponding to one sync with all the times since lastSync - * @param task - * @param sync - * @param timezone - * @return - */ - public static JsonObject taskToJsonObject(TimerTask task, SyncInfo sync, String timezone) { - JsonObject resultingObject = new JsonObject(); - JsonArray periodArray = new JsonArray(); - Date startDate = sync.getLastSync(); - Date endDate = DateUtils.ceiling(new Date(), Calendar.DAY_OF_MONTH); - String timestamp = "T00:00:00" + timezone; - SortedMap<Date, Long> dates = task.getAllDaysAndTimes().subMap(startDate, endDate); - LocalDate startPeriodDate = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); - String startPeriodString = startPeriodDate.toString() + timestamp; - LocalDate endPeriodDate = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); - String endPeriodString = endPeriodDate.toString() + timestamp; - //get the times of the task - periodArray.addAll(getTimesAsJsonArray(task, startDate, endDate, sync.isWithAnnotations(), timestamp)); - //if there are subtasks, get the times of the subtasks that have no syncInfo - if (!task.getSubTasks().isEmpty()) { - for (TimerTask subtask : getAllSubTasks(task)) { - if (subtask.getSynchronizingInfoList().isEmpty()) { - periodArray.addAll(getTimesAsJsonArray(subtask, startDate, endDate, sync.isWithAnnotations(), timestamp)); - } - } - } - resultingObject.addProperty("URL", sync.getSyncURL()); - resultingObject.addProperty("startDate", startPeriodString); - resultingObject.addProperty("endDate", endPeriodString); - resultingObject.add("periods", periodArray); - - return resultingObject; - - } - - /** - * Returns all the subtasks of a task (including the subtaks of subtasks) - * @param task - * @return a List<TimerTask> - */ - public static List<TimerTask> getAllSubTasks(TimerTask task) { - List<TimerTask> subTasksList = new ArrayList<>(); - if (!task.getSubTasks().isEmpty()) { - for (TimerTask subTask : task.getSubTasks()) { - subTasksList.add(subTask); - if (!subTask.getSubTasks().isEmpty()) { - subTasksList.addAll(getAllSubTasks(subTask)); - } - } - } - return subTasksList; - } - - /*** - * Returns a JsonArray of all the times of a task, according to format - * the id is made of the date of the time and the id of the task (as in the number given - * by jTimer) - * id, startdate, duration, info (if annotations is true) - * - * @param task - * @param startDate - * @param endDate - * @param withAnnotations - * @param timestamp - * @return a JsonArray of the periods - */ - public static JsonArray getTimesAsJsonArray(TimerTask task, Date startDate, Date endDate, boolean withAnnotations, String timestamp) { - JsonArray periodArray = new JsonArray(); - SortedMap<Date, Long> dates = task.getAllDaysAndTimes().subMap(startDate, endDate); - for (SortedMap.Entry<Date, Long> entry : dates.entrySet()) { - //adding id, startDate and duration - //converting Date to LocalDate (to ease the .toString()) - LocalDate date = entry.getKey().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); - String dateString = date.toString(); - //as jtimer has time entries only for a day, the id of the times will be the date in yyyy-mm-dd format - JsonObject periodElement = new JsonObject(); - periodElement.addProperty("id", dateString + "_" + task.getNumber()); - periodElement.addProperty("startDate", dateString + timestamp); - //TimerTaskHelper.getTotalTime(task, entry.getKey()) to get the total time of the task and all the subtasks - //entry.getValue() to get only the time of the task divided by 1000 (jtimer stores milliseconds) - periodElement.addProperty("duration", entry.getValue()/1000); - if (withAnnotations && !(getAnnotation(task, entry.getKey()).isEmpty())) { - StringBuilder annotationBuilder = new StringBuilder(); - for (String s : getAnnotation(task, entry.getKey())) { - annotationBuilder.append(s); - annotationBuilder.append(","); - } - periodElement.addProperty("info", annotationBuilder.toString()); - } - periodArray.add(periodElement); - } - return periodArray; - - } } diff --git a/src/main/java/org/chorem/jtimer/entities/package-info.java b/src/main/java/org/chorem/jtimer/entities/package-info.java index 7b740e0..d214e1d 100644 --- a/src/main/java/org/chorem/jtimer/entities/package-info.java +++ b/src/main/java/org/chorem/jtimer/entities/package-info.java @@ -2,7 +2,7 @@ * #%L * jTimer * %% - * Copyright (C) 2007 - 2011 CodeLutin, Chatellier Eric + * Copyright (C) 2007 - 2016 CodeLutin, Chatellier Eric * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as diff --git a/src/main/java/org/chorem/jtimer/io/AbstractSaver.java b/src/main/java/org/chorem/jtimer/io/AbstractSaver.java index f8d0ed7..1b0f528 100644 --- a/src/main/java/org/chorem/jtimer/io/AbstractSaver.java +++ b/src/main/java/org/chorem/jtimer/io/AbstractSaver.java @@ -22,16 +22,8 @@ package org.chorem.jtimer.io; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.TimerTask; -import org.apache.commons.io.FileUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Abstract saver class. * @@ -45,79 +37,4 @@ import org.apache.commons.logging.LogFactory; */ public abstract class AbstractSaver extends TimerTask implements Saver { - /** log. */ - private static Log log = LogFactory.getLog(GTimerIncrementalSaver.class); - - /** Backup file extension. */ - public static final String BACKUP_EXTENSION = ".tmp"; - - /** - * Make to backup of file if exists. - * - * Copy file to filename + ".tmp" - * - * @param file file to backup - * @return backup file or {@code null} if input file doesn't exist - * @throws IOException - */ - protected File makeBackupFile(File file) throws IOException { - - // if not exist, don't do anything - if (!file.exists()) { - return null; - } - - File backupFile = new File(file.getAbsoluteFile() + BACKUP_EXTENSION); - - if (log.isDebugEnabled()) { - log.debug("Backuping file " + file.getName() + " to " + backupFile.getName()); - } - - backupFile.delete(); - FileUtils.copyFile(file, backupFile); - return backupFile; - } - - /** - * Rename backup file to original file name; - * - * @param backupFile backup file - * @return if backup file has been restored - */ - protected boolean restoreBackupFile(File backupFile) { - - String fileName = backupFile.getAbsolutePath(); - if (!fileName.endsWith(BACKUP_EXTENSION)) { - throw new IllegalArgumentException("Not a valid backup file" + backupFile); - } - - fileName = fileName.substring(0, fileName.length() - BACKUP_EXTENSION.length()); - File file = new File(fileName); - - if (log.isDebugEnabled()) { - log.debug("Renaming " + backupFile.getName() + " to " + file.getName()); - } - - boolean done; - try { - Files.move(backupFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); - done = true; - } catch (IOException ex) { - done = false; - } - return done; - } - - /** - * Delete backup file. - * - * This function NEVER throw IOException. - * - * @param backupFile backup file (can be {@code null}) - */ - protected void deleteBackupFile(File backupFile) { - if (backupFile != null) { - backupFile.delete(); - } - } } diff --git a/src/main/java/org/chorem/jtimer/io/AbstractSaver.java b/src/main/java/org/chorem/jtimer/io/BackupUtils.java similarity index 62% copy from src/main/java/org/chorem/jtimer/io/AbstractSaver.java copy to src/main/java/org/chorem/jtimer/io/BackupUtils.java index f8d0ed7..58d0305 100644 --- a/src/main/java/org/chorem/jtimer/io/AbstractSaver.java +++ b/src/main/java/org/chorem/jtimer/io/BackupUtils.java @@ -1,66 +1,35 @@ -/* - * #%L - * jTimer - * %% - * Copyright (C) 2009 - 2016 CodeLutin, Chatellier Eric - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ - package org.chorem.jtimer.io; +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.util.TimerTask; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** - * Abstract saver class. - * - * Contains methods that can be used by various savers. - * - * @author chatellier - * @version $Revision$ - * - * Last update : $Date$ - * By : $Author$ + * Saver with backup support. */ -public abstract class AbstractSaver extends TimerTask implements Saver { +public class BackupUtils { /** log. */ - private static Log log = LogFactory.getLog(GTimerIncrementalSaver.class); + private static Log log = LogFactory.getLog(BackupUtils.class); /** Backup file extension. */ public static final String BACKUP_EXTENSION = ".tmp"; /** * Make to backup of file if exists. - * + * <p> * Copy file to filename + ".tmp" * * @param file file to backup * @return backup file or {@code null} if input file doesn't exist * @throws IOException */ - protected File makeBackupFile(File file) throws IOException { + public static File makeBackupFile(File file) throws IOException { // if not exist, don't do anything if (!file.exists()) { @@ -84,7 +53,7 @@ public abstract class AbstractSaver extends TimerTask implements Saver { * @param backupFile backup file * @return if backup file has been restored */ - protected boolean restoreBackupFile(File backupFile) { + public static boolean restoreBackupFile(File backupFile) { String fileName = backupFile.getAbsolutePath(); if (!fileName.endsWith(BACKUP_EXTENSION)) { @@ -110,12 +79,12 @@ public abstract class AbstractSaver extends TimerTask implements Saver { /** * Delete backup file. - * + * <p> * This function NEVER throw IOException. * * @param backupFile backup file (can be {@code null}) */ - protected void deleteBackupFile(File backupFile) { + public static void deleteBackupFile(File backupFile) { if (backupFile != null) { backupFile.delete(); } diff --git a/src/main/java/org/chorem/jtimer/io/GTimerIncrementalSaver.java b/src/main/java/org/chorem/jtimer/io/GTimerIncrementalSaver.java index e31a847..495dae6 100644 --- a/src/main/java/org/chorem/jtimer/io/GTimerIncrementalSaver.java +++ b/src/main/java/org/chorem/jtimer/io/GTimerIncrementalSaver.java @@ -71,7 +71,7 @@ import org.apache.commons.logging.LogFactory; import org.chorem.jtimer.JTimer; import org.chorem.jtimer.data.DataEventListener; import org.chorem.jtimer.data.DataViolationException; -import org.chorem.jtimer.entities.SyncInfo; +import org.chorem.jtimer.entities.TimerSync; import org.chorem.jtimer.entities.TimerAlert; import org.chorem.jtimer.entities.TimerAlert.Type; import org.chorem.jtimer.entities.TimerProject; @@ -121,9 +121,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, /** Extension alert. */ protected static final String GTIMER_ALERT_EXTENSION = "alert"; - /** Extension sync. */ - protected static final String JTIMER_SYNC_EXTENSION = "sync"; - /** Empty gtimer project name. */ protected static final String GTIMER_EMPTY_PROJECT_NAME = "No project"; @@ -355,7 +352,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, parseAnnotations(task); parseAlerts(task); - parseSyncInfo(task); } } catch (NumberFormatException e) { if (log.isWarnEnabled()) { @@ -594,39 +590,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, } } - /** Try and find sync information and load it - * - * @param task task to load sync info - */ - protected void parseSyncInfo(TimerTask task) throws IOException { - int taskNumber = task.getNumber(); - File syncTaskFile = new File(dataSaveDirectory + File.separator + taskNumber +"."+ GTIMER_TASK_EXTENSION +"."+ JTIMER_SYNC_EXTENSION); - - if (syncTaskFile.exists()) { - if (log.isDebugEnabled()) { - log.debug("Synchronization information found for task " + task.getName()); - } - try (BufferedReader parseIn = new BufferedReader(new FileReader(syncTaskFile))) { - JsonParser parser = new JsonParser(); - Gson gson = new Gson(); - JsonElement element = parser.parse(parseIn); - if (element.isJsonArray()) { - JsonArray infoArray = (JsonArray) element; - for (JsonElement obj : infoArray) { - JsonObject res = (JsonObject) obj; - SyncInfo sync = gson.fromJson(res, SyncInfo.class); - task.addSyncInfo(sync); - } - } - } - } else { - if (log.isDebugEnabled()) { - log.debug("Synchronization information not found for task " + task.getName()); - } - } - } - - /** * Find task alert and load it. * @@ -919,7 +882,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, try (Writer out = new OutputStreamWriter(new FileOutputStream(projectfile), "ISO-8859-1")) { // first try to make backup - backupfile = makeBackupFile(projectfile); + backupfile = BackupUtils.makeBackupFile(projectfile); // get creation date long mscreatedtime = project.getCreationDate().getTime(); @@ -931,7 +894,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, out.write("Created: " + createdTime + "\n"); out.write("Options: " + (project.isClosed() ? "1" : "0") + "\n"); - deleteBackupFile(backupfile); + BackupUtils.deleteBackupFile(backupfile); } catch (IOException e) { if (log.isDebugEnabled()) { log.error("Can't save project information, restore backup file", e); @@ -939,7 +902,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, // can be null if backup throw the exception if (backupfile != null) { - restoreBackupFile(backupfile); + BackupUtils.restoreBackupFile(backupfile); } } } @@ -1016,7 +979,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, try (Writer out = new OutputStreamWriter(new FileOutputStream(taskfile), "ISO-8859-1")) { // first make backup - backupfile = makeBackupFile(taskfile); + backupfile = BackupUtils.makeBackupFile(taskfile); // Format: 1.2 // Name: Test Tache 1.2 @@ -1050,7 +1013,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, } } - deleteBackupFile(backupfile); + BackupUtils.deleteBackupFile(backupfile); } catch (IOException e) { if (log.isErrorEnabled()) { log.error("Can't save task", e); @@ -1058,7 +1021,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, // can be null if backup throw the exception if (backupfile != null) { - restoreBackupFile(backupfile); + BackupUtils.restoreBackupFile(backupfile); } } } @@ -1082,7 +1045,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, try (Writer out = new OutputStreamWriter(new FileOutputStream(annotationTaskFile), "ISO-8859-1")) { // first make backup - backupfile = makeBackupFile(annotationTaskFile); + backupfile = BackupUtils.makeBackupFile(annotationTaskFile); // save time of each day for (Entry<Date, String> entry : task.getAllDaysAnnotations() @@ -1094,7 +1057,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, out.write(gtimerTS + " " + entry.getValue() + "\n"); } - deleteBackupFile(backupfile); + BackupUtils.deleteBackupFile(backupfile); } catch (IOException e) { if (log.isErrorEnabled()) { log.debug("Can't save task", e); @@ -1102,7 +1065,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, // can be null if backup throw the exception if (backupfile != null) { - restoreBackupFile(backupfile); + BackupUtils.restoreBackupFile(backupfile); } } } else { @@ -1110,50 +1073,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, } } - protected void saveSynchronizationInfo(TimerTask task) { - - int taskNumber = task.getNumber(); - - File synchronizationTaskFile = new File(dataSaveDirectory + File.separator - + taskNumber + "." + GTIMER_TASK_EXTENSION + "." + JTIMER_SYNC_EXTENSION); - - if ((task.getSynchronizingURLList() != null) || (!task.getSynchronizingURLList().isEmpty())) { - File backupfile = null; - - try (Writer out = new OutputStreamWriter(new FileOutputStream(synchronizationTaskFile), "ISO-8859-1")) { - - // first make backup - backupfile = makeBackupFile(synchronizationTaskFile); - - //make a json object for the syncInfo - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - gson.newJsonWriter(out); - JsonArray infoArray = new JsonArray(); - for (SyncInfo sync : task.getSynchronizingInfoList()) { - JsonElement res= gson.toJsonTree(sync); - infoArray.add(res); - } - out.write(gson.toJson(infoArray)); - - out.close(); - deleteBackupFile(backupfile); - if (log.isDebugEnabled()) { - log.debug("Saving sync on task : " + task.getName()); - } - } - catch (IOException e) { - if (log.isErrorEnabled()) { - log.debug("Can't save task synchronization information", e); - } - - // can be null if backup throws the exception - if (backupfile != null) { - restoreBackupFile(backupfile); - } - } - } - } - /** * Save task alerts. * @@ -1172,7 +1091,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, try (Writer out = new OutputStreamWriter(new FileOutputStream(alertTaskFile), "ISO-8859-1")) { // first make backup - backupfile = makeBackupFile(alertTaskFile); + backupfile = BackupUtils.makeBackupFile(alertTaskFile); out.write("Format: " + GTIMER_FILE_VERSION + "\n"); @@ -1192,7 +1111,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, } } - deleteBackupFile(backupfile); + BackupUtils.deleteBackupFile(backupfile); } catch (IOException e) { if (log.isErrorEnabled()) { log.debug("Can't save task", e); @@ -1200,7 +1119,7 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, // can be null if backup throw the exception if (backupfile != null) { - restoreBackupFile(backupfile); + BackupUtils.restoreBackupFile(backupfile); } } } else { @@ -1313,14 +1232,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, + alertFileToDelete.getPath() + ")"); } } - //and delete sync information file - File syncFileToDelete = new File(dataSaveDirectory + File.separator + fileNumber+ "."+ GTIMER_TASK_EXTENSION + "." + JTIMER_SYNC_EXTENSION); - if (syncFileToDelete.exists()) { - syncFileToDelete.delete(); - if (log.isDebugEnabled()) { - log.debug("Synchronization file deleted for " + taskOrProject.getName() + "(" + syncFileToDelete.getPath() +")"); - } - } } // second, go recursively on subtasks @@ -1366,7 +1277,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, // Potentiellement aussi lors d'un move. saveTaskAnnotation(task); saveAlerts(task); - saveSynchronizationInfo(task); // fix a bug with the gtimer subtask // save format du to composed task name @@ -1446,7 +1356,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, // remove task from running task and force save runningTasks.remove(task); saveTask(task); - saveSynchronizationInfo(task); } @Override @@ -1465,7 +1374,6 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, protected void saveRunningTasks() { synchronized (runningTasks) { runningTasks.forEach(this::saveTask); - runningTasks.forEach(this::saveSynchronizationInfo); } } @@ -1495,26 +1403,4 @@ public class GTimerIncrementalSaver extends AbstractSaver implements Saver, throw new DataViolationException("Can't add task", "vetoable.saver.invalid.characters"); } } - - /** when a syncInfo is deleted make a save - * - * @param task - * @param info - */ - @Override - public void syncInfoDeleted(TimerTask task, SyncInfo info) { - saveSynchronizationInfo(task); - } - - /** - * When a syncInfo is modified - * @param task TimerTask - * @param info - */ - @Override - public void syncInfoChanged(TimerTask task, SyncInfo info) { - saveSynchronizationInfo(task); - } - - } diff --git a/src/main/java/org/chorem/jtimer/io/TimerTaskSynchronizer.java b/src/main/java/org/chorem/jtimer/io/TimerTaskSynchronizer.java deleted file mode 100644 index 158ee15..0000000 --- a/src/main/java/org/chorem/jtimer/io/TimerTaskSynchronizer.java +++ /dev/null @@ -1,299 +0,0 @@ -/*- - * #%L - * jTimer - * %% - * Copyright (C) 2016 CodeLutin, Charlène Servantie - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ - -package org.chorem.jtimer.io; - -import com.google.gson.JsonObject; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.ProtocolException; -import java.net.URL; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Timer; -import org.apache.commons.lang3.time.DateUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.chorem.jtimer.data.DataEventListener; -import org.chorem.jtimer.entities.SyncInfo; -import org.chorem.jtimer.entities.TimerProject; -import org.chorem.jtimer.entities.TimerTask; -import org.chorem.jtimer.entities.TimerTaskHelper; - -/** - * - * Class dealing with synchronization of tasks - * - * Created by servantie on 01/06/16. - */ -public class TimerTaskSynchronizer implements DataEventListener { - - /** log */ - private static Log log = LogFactory.getLog(TimerTaskSynchronizer.class); - - /** timer to schedule syncs */ - protected Timer timer; - - /** Tasks to sync */ - protected Collection<TimerProject> projectsToSync; - - /** Timezone, defaulted to GMT +1 */ - protected String timezone = "+01:00"; - - /** - * TimerTaskSynchronizer constructor - */ - public TimerTaskSynchronizer() { - log.info("Starting synchronizer"); - } - - /** - * Change the timezone - * @param timezone - */ - public void setTimezone(String timezone) { - if (!timezone.isEmpty()) { - this.timezone = timezone; - } - } - - /** - * Does a sync attempt at start on tasks that have sync infos - * fixme:this sync doesn't trigger listener changes on the change of lastSync - */ - public void syncAtStart() { - if (log.isDebugEnabled()) { - log.debug("Sync At Start"); - } - - for (TimerProject project : projectsToSync) { - if (log.isDebugEnabled()) { - log.info("project : " + project.getName()); - } - - List<TimerTask> taskList = project.getSubTasks(); - for (TimerTask task : taskList) { - if (!task.getSynchronizingInfoList().isEmpty()) { - if (log.isDebugEnabled()) { - log.info("task : " + task.getName()); - } - synchronizeTask(task); - } - } - } - } - - - /** - * Synchronize for the first sync at startup - * (tests current date against lastSync date, - * syncs if the date is not the same day) - * @param task - * - */ - public void synchronizeTask(TimerTask task) { - for (SyncInfo syncInfo : task.getSynchronizingInfoList()) { - if (log.isDebugEnabled()) { - log.debug("Synchronization info : " + syncInfo.getSyncURL()); - } - //if the last sync is not on the same day - if (!DateUtils.isSameDay(new Date(), syncInfo.getLastSync())) { - String syncURL = syncInfo.getSyncURL(); - if (log.isDebugEnabled()) { - log.debug("Sync hasn't been done today for " + syncURL + " " + task.getName() ); - } - JsonObject syncObject = TimerTaskHelper.taskToJsonObject(task, syncInfo, timezone); - int syncResult = synchronizeTaskOnURL(syncObject); - if (syncResult > 199 && syncResult < 300) { - if (log.isDebugEnabled()) { - log.debug("Synchronization successful on : " + syncURL); - } - Calendar cal = Calendar.getInstance(); - syncInfo.setLastSync(cal.getTime()); - - } - else if (syncResult == 0) { - if (log.isDebugEnabled()) { - log.debug("Error in connection " + syncURL); - } - } - } - } - } - - /** - * Synchronizes one Task (calls to synchronizeTaskOnURL - * for each syncURLList element of the task) - * - * @param task - */ - public void synchronizeSingleTask(TimerTask task) { - - List<JsonObject> jsonObjectList = new ArrayList<>(); - for (SyncInfo syncInfo : task.getSynchronizingInfoList()) { - JsonObject syncObject = TimerTaskHelper.taskToJsonObject(task, syncInfo, timezone); - jsonObjectList.add(syncObject); - } - - for (JsonObject object : jsonObjectList) { - int syncResult = synchronizeTaskOnURL(object); - boolean successfulSync = false; - String syncURL = object.get("URL").getAsString(); - //syncResult is the HTTP Code received as an answer to the sync - if (syncResult > 199 && syncResult < 300) { - successfulSync = true; - } - //sync successful -> change the last sync time - if (successfulSync) { - if (log.isDebugEnabled()) { - log.debug("Sync successful on " + syncURL); - log.debug(LocalDateTime.now()); - } - Calendar cal = Calendar.getInstance(); - task.getSynchronizingInfo(syncURL).setLastSync(cal.getTime()); - } else { - if (log.isDebugEnabled()) { - log.debug("Error in connection " + syncURL); - log.debug("Error code received " + syncResult); - } - } - } - } - - /** - * Sends one synchronization (one JSON object) - * @param object the object to sync - * @return an int (http response code or 0 in case of a problem) - */ - public static int synchronizeTaskOnURL(JsonObject object) { - int upDateValue = 0; - //if it's an empty object, no synchronization is possible - if (!object.equals(new JsonObject())) { - String updateJsonString = object.toString(); - String syncURL = object.get("URL").getAsString(); - String charset = "UTF-8"; - HttpURLConnection connection; - URL url; - byte[] postDataBytes; - try { - url = new URL(syncURL); - connection = (HttpURLConnection) url.openConnection(); - connection.setUseCaches(false); - connection.setDoInput(true); - connection.setDoOutput(true); - connection.setRequestProperty("Content-Length", Integer.toString(updateJsonString.length())); - connection.setRequestProperty("Accept-Charset", charset); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestMethod("POST"); - postDataBytes = updateJsonString.getBytes(charset); - connection.getOutputStream().write(postDataBytes); - upDateValue = connection.getResponseCode(); - } catch (MalformedURLException e) { - if (log.isErrorEnabled()) { - log.error("URL malformed : " + syncURL + " Code : " + upDateValue); - } - } catch (ProtocolException e) { - if (log.isErrorEnabled()) { - log.error("Protocol error."+ " Code : " + upDateValue); - } - } catch (UnsupportedEncodingException e) { - if (log.isErrorEnabled()) { - log.error("Problem with encoding " + syncURL + " Code : " + upDateValue); - } - } catch (IOException e) { - if (log.isErrorEnabled()) { - log.error("Problem with the connection " + syncURL + " Code : " + upDateValue); - } - }catch (IllegalArgumentException e) { - if (log.isErrorEnabled()) { - log.error("Port value not valid " + syncURL + " Code : " + upDateValue); - } - } - } - return upDateValue; - } - - /** - * Tasks are synchronized when stopped - * @param task modified task - */ - @Override - public void stopTask(TimerTask task) { - if (!task.getSynchronizingInfoList().isEmpty()) { - if (log.isDebugEnabled()) { - log.debug("synchronizing task : " + task.getName()); - } - synchronizeSingleTask(task); - } else if (!task.getParent().getSynchronizingInfoList().isEmpty()) { - if (log.isDebugEnabled()) { - log.debug("synchronizing parent task : " + task.getParent().getName()); - } - synchronizeSingleTask(task.getParent()); - } - } - - /** - * When tasktime is modified, if it's before the last sync, - * the task is synchronized - */ - @Override - public void setTaskTime(TimerTask task, Date date, Long time) { - //check if it isn't the current day, to avoid sync every second - if (!DateUtils.isSameDay(new Date(), date)) { - if (!task.getSynchronizingInfoList().isEmpty()) { - for (SyncInfo syncInfo : task.getSynchronizingInfoList()) { - if (!DateUtils.isSameDay(date, syncInfo.getLastSync())) { - syncInfo.setLastSync(date); - } - } - synchronizeSingleTask(task); - } - //if the task has a parent task with a synchronizing info, then set that sync info - //to that time and try to sync - else if (!task.getParent().getSynchronizingInfoList().isEmpty()) { - TimerTask parentTask = task.getParent(); - for (SyncInfo syncInfo : parentTask.getSynchronizingInfoList()) { - if (!DateUtils.isSameDay(date, syncInfo.getLastSync())) { - syncInfo.setLastSync(date); - } - } - synchronizeSingleTask(parentTask); - } - } - } - - /** - * When data is loaded, get the data to work on and synchronize - */ - @Override - public void dataLoaded(Collection<TimerProject> projects) { - projectsToSync = projects; - syncAtStart(); - } - -} diff --git a/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleHelper.java b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleHelper.java new file mode 100644 index 0000000..8082470 --- /dev/null +++ b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleHelper.java @@ -0,0 +1,140 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie, Eric Chatellier + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.plugin.timebundle; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.apache.commons.lang3.time.DateUtils; +import org.chorem.jtimer.entities.TimerSync; +import org.chorem.jtimer.entities.TimerTask; +import org.chorem.jtimer.entities.TimerTaskHelper; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.SortedMap; + +/** + * Helper for time bundle. + */ +public class TimeBundleHelper { + + /** + * Returns all the subtasks of a task (including the subtaks of subtasks). + * + * @param task + * @return a List<TimerTask> + */ + public static List<TimerTask> getAllSubTasks(TimerTask task) { + List<TimerTask> subTasksList = new ArrayList<>(); + if (task.getSubTasks() != null) { + for (TimerTask subTask : task.getSubTasks()) { + subTasksList.add(subTask); + subTasksList.addAll(getAllSubTasks(subTask)); + } + } + return subTasksList; + } + + /** + * makes a JSONObject corresponding to one sync with all the times since lastSync. + * + * @param task + * @param sync + * @return + */ + public static JsonObject taskToJsonObject(TimerTask task, TimerSync sync) { + JsonObject resultingObject = new JsonObject(); + JsonArray periodArray = new JsonArray(); + Date startDate = sync.getLastSync(); + Date endDate = DateUtils.ceiling(new Date(), Calendar.DAY_OF_MONTH); + String timestamp = "T00:00:00+00:00"; // why +1 ? + SortedMap<Date, Long> dates = task.getAllDaysAndTimes().subMap(startDate, endDate); + LocalDate startPeriodDate = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + String startPeriodString = startPeriodDate.toString() + timestamp; + LocalDate endPeriodDate = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + String endPeriodString = endPeriodDate.toString() + timestamp; + //get the times of the task + periodArray.addAll(getTimesAsJsonArray(task, startDate, endDate, sync.isWithAnnotations(), timestamp)); + //if there are subtasks, get the times of the subtasks that have no syncInfo + if (!task.getSubTasks().isEmpty()) { + for (TimerTask subtask : getAllSubTasks(task)) { + if (subtask.getSyncs().isEmpty()) { + periodArray.addAll(getTimesAsJsonArray(subtask, startDate, endDate, sync.isWithAnnotations(), timestamp)); + } + } + } + resultingObject.addProperty("URL", sync.getUrl()); + resultingObject.addProperty("startDate", startPeriodString); + resultingObject.addProperty("endDate", endPeriodString); + resultingObject.add("periods", periodArray); + + return resultingObject; + + } + + /*** + * Returns a JsonArray of all the times of a task, according to format + * the id is made of the date of the time and the id of the task (as in the number given + * by jTimer) + * id, startdate, duration, info (if annotations is true) + * + * @param task + * @param startDate + * @param endDate + * @param withAnnotations + * @param timestamp + * @return a JsonArray of the periods + */ + public static JsonArray getTimesAsJsonArray(TimerTask task, Date startDate, Date endDate, boolean withAnnotations, String timestamp) { + JsonArray periodArray = new JsonArray(); + SortedMap<Date, Long> dates = task.getAllDaysAndTimes().subMap(startDate, endDate); + for (SortedMap.Entry<Date, Long> entry : dates.entrySet()) { + //adding id, startDate and duration + //converting Date to LocalDate (to ease the .toString()) + LocalDate date = entry.getKey().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + String dateString = date.toString(); + //as jtimer has time entries only for a day, the id of the times will be the date in yyyy-mm-dd format + JsonObject periodElement = new JsonObject(); + periodElement.addProperty("id", dateString + "_" + task.getNumber()); + periodElement.addProperty("startDate", dateString + timestamp); + //TimerTaskHelper.getTotalTime(task, entry.getKey()) to get the total time of the task and all the subtasks + //entry.getValue() to get only the time of the task divided by 1000 (jtimer stores milliseconds) + periodElement.addProperty("duration", entry.getValue() / 1000); + if (withAnnotations && !(TimerTaskHelper.getAnnotation(task, entry.getKey()).isEmpty())) { + StringBuilder annotationBuilder = new StringBuilder(); + for (String s : TimerTaskHelper.getAnnotation(task, entry.getKey())) { + annotationBuilder.append(s); + annotationBuilder.append(","); + } + periodElement.addProperty("info", annotationBuilder.toString()); + } + periodArray.add(periodElement); + } + return periodArray; + + } + +} diff --git a/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleSaver.java b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleSaver.java new file mode 100644 index 0000000..862fc50 --- /dev/null +++ b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleSaver.java @@ -0,0 +1,220 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie, Eric Chatellier + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.plugin.timebundle; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.JTimer; +import org.chorem.jtimer.data.DataEventListener; +import org.chorem.jtimer.entities.TimerProject; +import org.chorem.jtimer.entities.TimerSync; +import org.chorem.jtimer.entities.TimerTask; +import org.chorem.jtimer.io.BackupUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Collection; +import java.util.List; + +/** + * Saver to handle sync related info. + */ +public class TimeBundleSaver implements DataEventListener { + + private static Log log = LogFactory.getLog(TimeBundleSaver.class); + + protected static final String JTIMER_SYNC_DIRECTORY = "timebundle"; + + protected static final String JTIMER_PROJECT_EXTENSION = ".project.sync"; + + protected static final String JTIMER_TASK_EXTENSION = ".task.sync"; + + protected File syncDataDirectory; + + public TimeBundleSaver() { + syncDataDirectory = new File(JTimer.config.getHomeDirectory(), JTIMER_SYNC_DIRECTORY); + syncDataDirectory.mkdirs(); + } + + @Override + public void dataLoaded(Collection<TimerProject> projects) { + projects.forEach(this::addProjectSync); + } + + @Override + public void modifyProject(TimerProject project) { + saveProjectSync(project); + } + + @Override + public void modifyTask(TimerTask task) { + saveTaskSync(task); + } + + @Override + public void addProject(TimerProject project) { + saveProjectSync(project); + } + + @Override + public void addTask(TimerTask task) { + saveTaskSync(task); + } + + @Override + public void deleteProject(TimerProject project) { + int projectNumber = project.getNumber(); + File projectSyncFile = new File(syncDataDirectory, projectNumber + JTIMER_PROJECT_EXTENSION); + if (projectSyncFile.exists()) { + projectSyncFile.delete(); + if (log.isDebugEnabled()) { + log.debug("Synchronization file deleted for " + project.getName()); + } + } + } + + @Override + public void deleteTask(TimerTask task) { + int taskNumber = task.getNumber(); + File taskSyncFile = new File(syncDataDirectory, taskNumber + JTIMER_TASK_EXTENSION); + if (taskSyncFile.exists()) { + taskSyncFile.delete(); + if (log.isDebugEnabled()) { + log.debug("Synchronization file deleted for " + task.getName()); + } + } + } + + protected void addProjectSync(TimerProject timerProject) { + int projectNumber = timerProject.getNumber(); + File projectSyncFile = new File(syncDataDirectory, projectNumber + JTIMER_PROJECT_EXTENSION); + parseFileSync(timerProject, projectSyncFile); + timerProject.getSubTasks().forEach(this::addTaskSync); + } + + protected void addTaskSync(TimerTask timerTask) { + int taskNumber = timerTask.getNumber(); + File taskSyncFile = new File(syncDataDirectory, taskNumber + JTIMER_TASK_EXTENSION); + parseFileSync(timerTask, taskSyncFile); + timerTask.getSubTasks().forEach(this::addTaskSync); + } + + protected void parseFileSync(TimerTask projectOrTask, File syncFile) { + if (syncFile.exists()) { + if (log.isDebugEnabled()) { + log.debug("Add sync for project " + projectOrTask.getName()); + } + + try (Reader parseIn = new BufferedReader(new FileReader(syncFile))) { + JsonParser parser = new JsonParser(); + Gson gson = new Gson(); + JsonElement element = parser.parse(parseIn); + JsonArray infoArray = (JsonArray) element; + for (JsonElement obj : infoArray) { + JsonObject res = (JsonObject) obj; + TimerSync sync = gson.fromJson(res, TimerSync.class); + projectOrTask.addSync(sync); + } + } catch (IOException ex) { + if (log.isErrorEnabled()) { + log.error("Can't parse sync file", ex); + } + } + } else if (log.isTraceEnabled()) { + log.trace("Sync not found for " + projectOrTask.getName()); + } + } + + protected void saveProjectSync(TimerProject timerProject) { + int projectNumber = timerProject.getNumber(); + File projectSyncFile = new File(syncDataDirectory, projectNumber + JTIMER_PROJECT_EXTENSION); + saveFileSync(timerProject, projectSyncFile); + } + + protected void saveTaskSync(TimerTask timerTask) { + int taskNumber = timerTask.getNumber(); + File taskSyncFile = new File(syncDataDirectory, taskNumber + JTIMER_TASK_EXTENSION); + saveFileSync(timerTask, taskSyncFile); + } + + protected void saveFileSync(TimerTask task, File taskSyncFile) { + + if (CollectionUtils.isEmpty(task.getSyncs())) { + taskSyncFile.delete(); + + } else { + File backupfile = null; + + try (Writer out = new OutputStreamWriter(new FileOutputStream(taskSyncFile), "ISO-8859-1")) { + + // first make backup + backupfile = BackupUtils.makeBackupFile(taskSyncFile); + + //make a json object for the syncInfo + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + out.write(gson.toJson(task.getSyncs())); + + out.close(); + BackupUtils.deleteBackupFile(backupfile); + if (log.isDebugEnabled()) { + log.debug("Saving sync on task : " + task.getName()); + } + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.debug("Can't save task synchronization information", e); + } + + // can be null if backup throws the exception + if (backupfile != null) { + BackupUtils.restoreBackupFile(backupfile); + } + } + } + } + + @Override + public void postMergeTasks(TimerTask destinationTask, List<TimerTask> otherTasks) { + otherTasks.forEach(task -> { + task.getSyncs().forEach(sync -> { + boolean urlExists = destinationTask.getSyncs().stream() // + .anyMatch(timerSync -> timerSync.getUrl().equals(sync.getUrl())); + if (!urlExists) { + destinationTask.getSyncs().add(sync); + } + }); + }); + modifyTask(destinationTask); + } +} diff --git a/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleSynchronizer.java b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleSynchronizer.java new file mode 100644 index 0000000..abd5fba --- /dev/null +++ b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleSynchronizer.java @@ -0,0 +1,148 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie, Eric Chatellier + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.plugin.timebundle; + +import com.google.gson.JsonObject; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.data.DataEventListener; +import org.chorem.jtimer.entities.TimerProject; +import org.chorem.jtimer.entities.TimerSync; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Collection; +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; + +/** + * Synchronizer background task. + */ +public class TimeBundleSynchronizer extends TimerTask implements DataEventListener { + + /** log */ + private static Log log = LogFactory.getLog(TimeBundleSynchronizer.class); + + /** timer to schedule syncs */ + protected Timer timer; + + protected Collection<TimerProject> projects; + + /** + * TimeBundleSynchronizer constructor + */ + public TimeBundleSynchronizer() { + log.info("Starting synchronizer"); + timer = new Timer(); + } + + @Override + public void dataLoaded(Collection<TimerProject> projects) { + this.projects = projects; + timer.schedule(this, 0, 60 * 60 * 1000); + } + + @Override + public void run() { + projects.forEach(this::synchronizerTask); + } + + protected void synchronizerTask(org.chorem.jtimer.entities.TimerTask timerTask) { + synchronizerProjectOrTask(timerTask); + timerTask.getSubTasks().forEach(this::synchronizerTask); + } + + protected void synchronizerProjectOrTask(org.chorem.jtimer.entities.TimerTask timerTask) { + timerTask.getSyncs().forEach(timerSync -> { + if (!DateUtils.isSameDay(new Date(), timerSync.getLastSync())) { + if (log.isDebugEnabled()) { + log.debug("Synchronizing task " + timerTask.getName() + "..."); + } + + JsonObject syncObject = TimeBundleHelper.taskToJsonObject(timerTask, timerSync); + synchronizeTaskOnURL(syncObject); + } + }); + } + + /** + * Sends one synchronization (one JSON object) + * @param object the object to sync + * @return an int (http response code or 0 in case of a problem) + */ + protected int synchronizeTaskOnURL(JsonObject object) { + int upDateValue = 0; + // if it's an empty object, no synchronization is possible + if (!object.equals(new JsonObject())) { + String updateJsonString = object.toString(); + String syncURL = object.get("URL").getAsString(); + String charset = "UTF-8"; + HttpURLConnection connection; + URL url; + byte[] postDataBytes; + try { + url = new URL(syncURL); + connection = (HttpURLConnection) url.openConnection(); + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Length", Integer.toString(updateJsonString.length())); + connection.setRequestProperty("Accept-Charset", charset); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestMethod("POST"); + postDataBytes = updateJsonString.getBytes(charset); + connection.getOutputStream().write(postDataBytes); + upDateValue = connection.getResponseCode(); + } catch (IOException ex) { + if (log.isErrorEnabled()) { + log.error("Problem with the connection " + syncURL + " Code : " + upDateValue, ex); + } + } + } + return upDateValue; + } + + /** + * When tasktime is modified, if it's before the last sync, + * the task is synchronized + */ + @Override + public void setTaskTime(org.chorem.jtimer.entities.TimerTask task, Date date, Long time) { + //check if it isn't the current day, to avoid sync every second + if (!DateUtils.isSameDay(new Date(), date)) { + if (!task.getSyncs().isEmpty()) { + for (TimerSync timerSync : task.getSyncs()) { + if (!DateUtils.isSameDay(date, timerSync.getLastSync())) { + timerSync.setLastSync(date); + } + } + } + + if (task.getParent() != null) { + setTaskTime(task.getParent(), date, time); + } + } + } +} diff --git a/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleVetoable.java b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleVetoable.java new file mode 100644 index 0000000..b4f34e5 --- /dev/null +++ b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimeBundleVetoable.java @@ -0,0 +1,61 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie, Eric Chatellier + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.plugin.timebundle; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.data.DataEventListener; +import org.chorem.jtimer.data.DataViolationException; +import org.chorem.jtimer.data.VetoableDataEventListener; +import org.chorem.jtimer.entities.TimerSync; +import org.chorem.jtimer.entities.TimerTask; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Vetoable for forbidden action on synchronized tasks. + */ +public class TimeBundleVetoable implements VetoableDataEventListener { + + private static Log log = LogFactory.getLog(TimeBundleVetoable.class); + + /** + * Merge project violation key. + */ + protected static final String INVALID_SYNC_LIST_VIOLATION = "vetoable.timebundle.invalid.sync.list"; + + @Override + public void checkMergeTasks(TimerTask destinationTask, List<TimerTask> otherTasks) { + Set<String> allUrls = otherTasks.stream() // + .flatMap(t -> t.getSyncs().stream()) // + .map(TimerSync::getUrl) // + .collect(Collectors.toSet()); + Set<String> destUrls = destinationTask.getSyncs().stream() // + .map(TimerSync::getUrl) // + .collect(Collectors.toSet()); + if (!allUrls.equals(destUrls)) { + throw new DataViolationException("Can't merge tasks", INVALID_SYNC_LIST_VIOLATION); + } + } +} diff --git a/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncCellRenderer.java b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncCellRenderer.java new file mode 100644 index 0000000..87efc37 --- /dev/null +++ b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncCellRenderer.java @@ -0,0 +1,46 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie, Eric Chatellier + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.plugin.timebundle; + +import javax.swing.table.DefaultTableCellRenderer; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Renderer for last sync date column. + */ +public class TimerSyncCellRenderer extends DefaultTableCellRenderer { + + private Date dateValue; + private SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + private String valueToString = ""; + + @Override + public void setValue(Object value) { + if ((value != null)) { + dateValue = (Date) value; + valueToString = dateFormat.format(dateValue); + value = valueToString; + super.setValue(value); + } + } +} diff --git a/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncEditor.java b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncEditor.java new file mode 100644 index 0000000..4ece14f --- /dev/null +++ b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncEditor.java @@ -0,0 +1,245 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie, Eric Chatellier + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.plugin.timebundle; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.TableColumnModel; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.data.TimerCore; +import org.chorem.jtimer.entities.TimerProject; +import org.chorem.jtimer.entities.TimerSync; +import org.chorem.jtimer.entities.TimerTask; +import org.jdesktop.application.Application; +import org.jdesktop.application.FrameView; + +/** + * UI to modify Synchronization Info of a task. + */ +public class TimerSyncEditor extends FrameView implements ActionListener { + + /** + * Class logger + */ + protected static Log log = LogFactory.getLog(TimerSyncEditor.class); + + /** + * Timer core. + */ + protected TimerCore core; + + /** + * button to add a URL + */ + protected JButton addButton; + + /** + * button to delete a URL + */ + protected JButton deleteButton; + + /** + * button to test a url + */ + protected JButton testSyncUrlButton; + + /** + * JTable of URLs + */ + protected JTable urlJTable; + + /** + * task to update + */ + protected TimerTask task; + + /** + * timezone + */ + protected String timezone; + + /** + * UpdaterView constructor. + * + * @param application parent reference + * @param core core reference + * @param task the task to update + */ + public TimerSyncEditor(Application application, TimerCore core, TimerTask task) { + + super(application); + // modify frame name + getFrame().setName("syncFrame"); + getFrame().setTitle(getResourceMap().getString("syncTitle")); + + this.core = core; + this.task = task; + timezone = "+01:00"; + + setComponent(getMainComponent()); + } + + /** + * Get main view component. + * + * @return main component + */ + protected JComponent getMainComponent() { + + JPanel configComponent = new JPanel(); + configComponent.setLayout(new BorderLayout(5, 5)); + + JPanel urlPanel = new JPanel(); + urlPanel.setLayout(new BorderLayout(5,5)); + + urlJTable = new JTable(new TimerSyncTableModel(this, task)); + //try to have coherent width of columns + TableColumnModel tableColModel = urlJTable.getColumnModel(); + tableColModel.getColumn(0).setPreferredWidth(200); + tableColModel.getColumn(1).setPreferredWidth(40); + tableColModel.getColumn(2).setPreferredWidth(60); + tableColModel.getColumn(3).setPreferredWidth(200); + + //render the date properly + tableColModel.getColumn(3).setCellRenderer(new TimerSyncCellRenderer()); + + JScrollPane scrollTablePane = new JScrollPane(urlJTable); + configComponent.add(scrollTablePane, BorderLayout.CENTER); + + //panel to hold the buttons + JPanel buttonsPane = new JPanel(new BorderLayout()); + JPanel buttonPane = new JPanel(); + buttonPane.setAlignmentX(Component.CENTER_ALIGNMENT); + + addButton = new JButton(getResourceMap().getString("addButton")); + addButton.addActionListener(this); + addButton.setActionCommand("addURL"); + + deleteButton = new JButton(getResourceMap().getString("deleteButton")); + deleteButton.addActionListener(this); + deleteButton.setActionCommand("deleteURL"); + + testSyncUrlButton = new JButton(getResourceMap().getString("testSyncButton")); + testSyncUrlButton.addActionListener(this); + testSyncUrlButton.setActionCommand("testURL"); + + buttonPane.add(addButton); + buttonPane.add(testSyncUrlButton); + buttonPane.add(deleteButton); + // button to close + JButton closeButton = new JButton(); + closeButton.setAction(getContext().getActionMap(this).get("closeView")); + + //adding components to the main one + buttonsPane.add(buttonPane, BorderLayout.CENTER); + buttonsPane.add(closeButton, BorderLayout.SOUTH); + configComponent.add(buttonsPane, BorderLayout.SOUTH); + + // color fix on linux ? + configComponent.setBackground(urlPanel.getBackground()); + // set minimum size to prevent "packed size" (too big) + configComponent.setMinimumSize(new Dimension(200, 200)); + + return configComponent; + } + + /** + * Close action. + */ + @org.jdesktop.application.Action + public void closeView() { + TimerSyncTableModel model = (TimerSyncTableModel) urlJTable.getModel(); + task.setSyncs(model.getTimerSyncList()); + if (task instanceof TimerProject) { + core.getData().modifyProject(TimerProject.class.cast(task)); + } else { + core.getData().modifyTask(task); + } + getApplication().hide(this); + } + + /** + * Method to display an error message + * + * @param errorMessage the message to display + * @param titleBar the title of the frame + */ + public static void errorBox(String errorMessage, String titleBar) { + JOptionPane.showMessageDialog(null, errorMessage, titleBar, JOptionPane.ERROR_MESSAGE); + } + + /** + * Method to display an info message + * + * @param infoMessage the message to display + * @param titleBar the title of the frame + */ + public static void infoBox(String infoMessage, String titleBar) { + JOptionPane.showMessageDialog(null, infoMessage, titleBar, JOptionPane.INFORMATION_MESSAGE); + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + + String actionCommand = actionEvent.getActionCommand(); + TimerSyncTableModel model = (TimerSyncTableModel) urlJTable.getModel(); + + if ("addURL".equals(actionCommand)) { + model.add(new TimerSync("")); + } + else if ("deleteURL".equals(actionCommand)) { + TimerSync timerSync = model.getValueAt(urlJTable.getSelectedRow()); + model.remove(timerSync); + } + /*else if ("testURL".equals(actionCommand)) { + //if the test button has been clicked, test the sync on the URL and returns a message to the user + //tests only if the info exists + if (task.getSynchronizingInfoList().contains(infoToUse)) { + JsonObject testObject = TimerTaskHelper.taskToJsonObject(task, infoToUse, timezone); + int responseCode = TimerTaskSynchronizer.synchronizeTaskOnURL(testObject); + + if (responseCode > 199 && responseCode < 300) { + infoBox(getResourceMap().getString("testSyncSuccessMessage"), getResourceMap().getString("testSyncSuccessTitle")); + Calendar cal = Calendar.getInstance(); + infoToUse.setLastSync(cal.getTime()); + core.getData().syncInfoChanged(task, infoToUse); + } else { + errorBox(getResourceMap().getString("testSyncFailureMessage"), getResourceMap().getString("testSyncFailureTitle")); + } + + if (log.isDebugEnabled()) { + log.debug("Response code : " + responseCode); + } + } + } */ + } +} diff --git a/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncTableModel.java b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncTableModel.java new file mode 100644 index 0000000..94f100e --- /dev/null +++ b/src/main/java/org/chorem/jtimer/plugin/timebundle/TimerSyncTableModel.java @@ -0,0 +1,203 @@ +/*- + * #%L + * jTimer + * %% + * Copyright (C) 2016 CodeLutin, Charlène Servantie, Eric Chatellier + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +package org.chorem.jtimer.plugin.timebundle; + +import org.chorem.jtimer.entities.TimerSync; +import org.chorem.jtimer.entities.TimerTask; +import org.jdesktop.application.View; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Class to deal with the syncs in a table. + */ +public class TimerSyncTableModel extends AbstractTableModel { + + /** the syncInfo list. */ + protected List<TimerSync> timerSyncList; + + /** headers of the columns. */ + protected String[] columnHeaders; + + public TimerSyncTableModel(View view, TimerTask task) { + timerSyncList = new ArrayList<>(task.getSyncs()); + columnHeaders = new String[] { + view.getResourceMap().getString("syncURLHeader"), + view.getResourceMap().getString("activeHeader"), + view.getResourceMap().getString("withAnnotationsHeader"), + view.getResourceMap().getString("lastSyncHeader") + }; + } + + /** + * Gets the column headers + * + * @param col the column + * @return a string for the header + */ + @Override + public String getColumnName(int col) { + return columnHeaders[col]; + } + + + @Override + public int getRowCount() { + return timerSyncList.size(); + } + + /** + * returns the column number (url, activity, annotations and last sync, so 4) + */ + @Override + public int getColumnCount() { + return columnHeaders.length; + } + + /** + * Returns the value at rowIndex, columnIndex + * column 0 is url String, column 1 is activity boolean, + * column 2 is annotations boolean, column 3 is lastSync Date + * + * @param rowIndex + * @param columnIndex + * @return an Object + */ + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + Object value = ""; + TimerSync timerSync = timerSyncList.get(rowIndex); + switch (columnIndex) { + case 0: + value = timerSync.getUrl(); + break; + case 1: + value = timerSync.isActive(); + break; + case 2: + value = timerSync.isWithAnnotations(); + break; + case 3: + value = timerSync.getLastSync(); + break; + } + return value; + } + + /** + * When the data is changed + * + * @param value an object -a string- + * @param rowIndex the row selected + * @param columnIndex the column modified + */ + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + TimerSync timerSync = timerSyncList.get(rowIndex); + // if the url changes + switch (columnIndex) { + case 0: + timerSync.setUrl((String) value); + break; + case 1: + timerSync.setActive((Boolean) value); + break; + case 2: + timerSync.setWithAnnotations((Boolean) value); + break; + } + } + + /** + * To mark the url and checkbox cells as editable but not the date + * + * @param rowIndex + * @param columnIndex + * @return + */ + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + // the sync date is not editable + return columnIndex < 3; + } + + /** + * Returns the class of objects in the columns + * + * @param columnIndex + * @return the class + */ + + @Override + public Class<?> getColumnClass(int columnIndex) { + Class<?> result; + switch (columnIndex) { + case 0: + result = String.class; + break; + case 1: + case 2: + result = Boolean.class; + break; + case 3: + result = Date.class; + break; + default: + result = Object.class; + break; + } + + return result; + } + + /** + * Adds a syncInfo + * + * @param sync + */ + public void add(TimerSync sync) { + timerSyncList.add(sync); + fireTableRowsInserted(timerSyncList.indexOf(sync), timerSyncList.indexOf(sync)); + } + + /** + * removes a syncInfo + * + * @param sync + */ + public void remove(TimerSync sync) { + int index = timerSyncList.indexOf(sync); + timerSyncList.remove(sync); + fireTableRowsDeleted(index, index); + } + + public TimerSync getValueAt(int selectedRow) { + return timerSyncList.get(selectedRow); + } + + public List<TimerSync> getTimerSyncList() { + return timerSyncList; + } +} diff --git a/src/main/java/org/chorem/jtimer/ui/report/TimerTaskSyncInfoEditor.java b/src/main/java/org/chorem/jtimer/ui/report/TimerTaskSyncInfoEditor.java deleted file mode 100644 index 81b8701..0000000 --- a/src/main/java/org/chorem/jtimer/ui/report/TimerTaskSyncInfoEditor.java +++ /dev/null @@ -1,453 +0,0 @@ -/* - * #%L - * jTimer - * %% - * Copyright (C) 2008 - 2016 CodeLutin, Chatellier Eric - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ -package org.chorem.jtimer.ui.report; - -import com.google.gson.JsonObject; -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableColumnModel; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.chorem.jtimer.data.TimerCore; -import org.chorem.jtimer.entities.SyncInfo; -import org.chorem.jtimer.entities.TimerTask; -import org.chorem.jtimer.entities.TimerTaskHelper; -import org.chorem.jtimer.io.TimerTaskSynchronizer; -import org.jdesktop.application.Application; -import org.jdesktop.application.FrameView; - -/** - * UI to modify Synchronization Info of a task - * - * Created by servantie on 13/05/16. - */ -public class TimerTaskSyncInfoEditor extends FrameView implements ActionListener { - - /** - * Class logger - */ - protected static Log log = LogFactory.getLog(TimerTaskSyncInfoEditor.class); - - /** - * Timer core. - */ - protected TimerCore core; - - /** - * button to add a URL - */ - protected JButton addButton; - - /** - * button to delete a URL - */ - protected JButton deleteButton; - - /** - * button to test a url - */ - protected JButton testSyncUrlButton; - - /** - * JTable of URLs - */ - protected JTable urlJTable; - - /** - * task to update - */ - protected TimerTask task; - - /** - * timezone - */ - protected String timezone; - - /** - * UpdaterView constructor. - * - * @param application parent reference - * @param core core reference - * @param task the task to update - */ - public TimerTaskSyncInfoEditor(Application application, TimerCore core, TimerTask task) { - - super(application); - // modify frame name - getFrame().setName("syncFrame"); - getFrame().setTitle(getResourceMap().getString("syncTitle")); - - this.core = core; - this.task = task; - timezone = "+01:00"; - - setComponent(getMainComponent()); - } - - /** - * Get main view component. - * - * @return main component - */ - protected JComponent getMainComponent() { - - JPanel configComponent = new JPanel(); - configComponent.setLayout(new BorderLayout(5, 5)); - - JPanel urlPanel = new JPanel(); - urlPanel.setLayout(new BorderLayout(5,5)); - - urlJTable = new JTable(new SyncInfoTableModel()); - //try to have coherent width of columns - TableColumnModel tableColModel = urlJTable.getColumnModel(); - tableColModel.getColumn(0).setPreferredWidth(200); - tableColModel.getColumn(1).setPreferredWidth(40); - tableColModel.getColumn(2).setPreferredWidth(60); - tableColModel.getColumn(3).setPreferredWidth(200); - - //render the date properly - tableColModel.getColumn(3).setCellRenderer(new DateRenderer()); - - JScrollPane scrollTablePane = new JScrollPane(urlJTable); - configComponent.add(scrollTablePane, BorderLayout.CENTER); - - //panel to hold the buttons - JPanel buttonsPane = new JPanel(new BorderLayout()); - JPanel buttonPane = new JPanel(); - buttonPane.setAlignmentX(Component.CENTER_ALIGNMENT); - - addButton = new JButton(getResourceMap().getString("addButton")); - addButton.addActionListener(this); - addButton.setActionCommand("addURL"); - - deleteButton = new JButton(getResourceMap().getString("deleteButton")); - deleteButton.addActionListener(this); - deleteButton.setActionCommand("deleteURL"); - - testSyncUrlButton = new JButton(getResourceMap().getString("testSyncButton")); - testSyncUrlButton.addActionListener(this); - testSyncUrlButton.setActionCommand("testURL"); - - buttonPane.add(addButton); - buttonPane.add(testSyncUrlButton); - buttonPane.add(deleteButton); - // button to close - JButton closeButton = new JButton(); - closeButton.setAction(getContext().getActionMap(this).get("closeView")); - - //adding components to the main one - buttonsPane.add(buttonPane, BorderLayout.CENTER); - buttonsPane.add(closeButton, BorderLayout.SOUTH); - configComponent.add(buttonsPane, BorderLayout.SOUTH); - - // color fix on linux ? - configComponent.setBackground(urlPanel.getBackground()); - // set minimum size to prevent "packed size" (too big) - configComponent.setMinimumSize(new Dimension(200, 200)); - - return configComponent; - } - - /** - * Close action. - */ - @org.jdesktop.application.Action - public void closeView() { - getApplication().hide(this); - } - - /** - * Method to display an error message - * - * @param errorMessage the message to display - * @param titleBar the title of the frame - */ - public static void errorBox(String errorMessage, String titleBar) { - JOptionPane.showMessageDialog(null, errorMessage, titleBar, JOptionPane.ERROR_MESSAGE); - } - - /** - * Method to display an info message - * - * @param infoMessage the message to display - * @param titleBar the title of the frame - */ - public static void infoBox(String infoMessage, String titleBar) { - JOptionPane.showMessageDialog(null, infoMessage, titleBar, JOptionPane.INFORMATION_MESSAGE); - } - - /** - * Class to deal with the SyncInfos in a table - */ - protected class SyncInfoTableModel extends AbstractTableModel { - - /** - * the syncInfo list - */ - protected List<SyncInfo> syncInfoList; - - /** - * headers of the columns - */ - protected String[] columnHeaders = {getResourceMap().getString("syncURLHeader"), getResourceMap().getString("isActiveHeader"), getResourceMap().getString("isWithAnnotationsHeader"), getResourceMap().getString("lastSyncHeader")}; - - - public SyncInfoTableModel() { - syncInfoList = task.getSynchronizingInfoList(); - } - - - /** - * Gets the column headers - * - * @param col the column - * @return a string for the header - */ - @Override - public String getColumnName(int col) { - return columnHeaders[col]; - } - - - @Override - public int getRowCount() { - return syncInfoList.size(); - } - - /** - * returns the column number (url, activity, annotations and last sync, so 4) - */ - @Override - public int getColumnCount() { - return 4; - } - - /** - * Returns the value at rowIndex, columnIndex - * column 0 is url String, column 1 is activity boolean, - * column 2 is annotations boolean, column 3 is lastSync Date - * - * @param rowIndex - * @param columnIndex - * @return an Object - */ - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - Object value = ""; - SyncInfo syncInfo = syncInfoList.get(rowIndex); - switch (columnIndex) { - case 0: - value = syncInfo.getSyncURL(); - break; - case 1: - value = syncInfo.isActiveSync(); - break; - case 2: - value = syncInfo.isWithAnnotations(); - break; - case 3: - value = syncInfo.getLastSync(); - break; - } - return value; - } - - /** - * When the data is changed - * - * @param value an object -a string- - * @param rowIndex the row selected - * @param columnIndex the column modified - */ - @Override - public void setValueAt(Object value, int rowIndex, int columnIndex) { - SyncInfo syncInfo = syncInfoList.get(rowIndex); - //if the url changes - switch (columnIndex) { - case 0: - syncInfo.setSyncURL((String) value); - break; - case 1: - syncInfo.setActiveSync((Boolean) value); - break; - case 2: - syncInfo.setWithAnnotations((Boolean) value); - break; - } - core.getData().syncInfoChanged(task, syncInfo); - } - - /** - * To mark the url and checkbox cells as editable but not the date - * - * @param rowIndex - * @param columnIndex - * @return - */ - @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - //the sync date is not editable - if (columnIndex >= 3) { - return false; - } else { - return true; - } - } - - /** - * Returns the class of objects in the columns - * - * @param columnIndex - * @return the class - */ - - @Override - public Class<?> getColumnClass(int columnIndex) { - if (columnIndex == 0) { - return String.class; - } else if (columnIndex == 1 || columnIndex == 2) { - return Boolean.class; - } else if (columnIndex == 3) { - return Date.class; - } - return Object.class; - } - - /** - * Adds a syncInfo - * - * @param sync - */ - public void add(SyncInfo sync) { - if (!syncInfoList.contains(sync)) { - syncInfoList.add(sync); - fireTableRowsInserted(syncInfoList.indexOf(sync), syncInfoList.indexOf(sync)); - } - } - - /** - * removes a syncInfo - * - * @param sync - */ - public void remove(SyncInfo sync) { - if (syncInfoList.contains(sync)) { - syncInfoList.remove(sync); - fireTableRowsDeleted(syncInfoList.indexOf(sync), syncInfoList.indexOf(sync)); - } - } - } - - /** - * Class to render dates with a readable format - * (dd/MM/yy HH:mm:ss) - */ - protected class DateRenderer extends DefaultTableCellRenderer { - - private Date dateValue; - private SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss"); - private String valueToString = ""; - - @Override - public void setValue(Object value) { - if ((value != null)) { - dateValue = (Date) value; - valueToString = dateFormat.format(dateValue); - value = valueToString; - super.setValue(value); - } - } - } - - @Override - public void actionPerformed(ActionEvent actionEvent) { - - String actionCommand = actionEvent.getActionCommand(); - SyncInfo infoToUse; - SyncInfoTableModel model = (SyncInfoTableModel) urlJTable.getModel(); - - if ("addURL".equals(actionCommand)) { - model.add(new SyncInfo("")); - } else if (!task.getSynchronizingInfoList().isEmpty()) { - //first check that a row is selected - if (urlJTable.getSelectedRow() != -1) { - infoToUse = task.getSynchronizingInfo((String) urlJTable.getModel().getValueAt(urlJTable.getSelectedRow(), 0)); - if ("deleteURL".equals(actionCommand)) { - if (!((SyncInfoTableModel) urlJTable.getModel()).syncInfoList.isEmpty()) { - //if the delete button has been clicked, delete the task (if it exists) - if ((task.getSynchronizingInfoList().contains(infoToUse))) { - task.removeSyncInfo(infoToUse); - model.remove(infoToUse); - core.getData().deleteSyncInfo(task, infoToUse); - urlJTable.repaint(); - } else { - errorBox(getResourceMap().getString("deleteErrorMessage"), getResourceMap().getString("deleteErrorTitle")); - } - - } else { - errorBox(getResourceMap().getString("deleteErrorMessage"), getResourceMap().getString("deleteErrorTitle")); - } - - } else if ("testURL".equals(actionCommand)) { - //if the test button has been clicked, test the sync on the URL and returns a message to the user - //tests only if the info exists - if (task.getSynchronizingInfoList().contains(infoToUse)) { - JsonObject testObject = TimerTaskHelper.taskToJsonObject(task, infoToUse, timezone); - int responseCode = TimerTaskSynchronizer.synchronizeTaskOnURL(testObject); - - if (responseCode > 199 && responseCode < 300) { - infoBox(getResourceMap().getString("testSyncSuccessMessage"), getResourceMap().getString("testSyncSuccessTitle")); - Calendar cal = Calendar.getInstance(); - infoToUse.setLastSync(cal.getTime()); - core.getData().syncInfoChanged(task, infoToUse); - } else { - errorBox(getResourceMap().getString("testSyncFailureMessage"), getResourceMap().getString("testSyncFailureTitle")); - } - - if (log.isDebugEnabled()) { - log.debug("Response code : " + responseCode); - } - } - } - } - } else { - if (log.isDebugEnabled()) { - log.debug("Action performed. Action: " + actionCommand); - } - } - } -} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 39ee61a..d82c2d6 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -30,6 +30,7 @@ <Loggers> <Logger name="org.chorem.jtimer" level="info"/> + <Logger name="org.chorem.jtimer.plugin.timebundle" level="debug"/> <Root level="warn"> <AppenderRef ref="Console" /> diff --git a/src/main/resources/org/chorem/jtimer/ui/report/resources/TimerTaskSyncInfoEditor.properties b/src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/TimerSyncEditor.properties similarity index 93% rename from src/main/resources/org/chorem/jtimer/ui/report/resources/TimerTaskSyncInfoEditor.properties rename to src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/TimerSyncEditor.properties index f311f08..b2b24ff 100644 --- a/src/main/resources/org/chorem/jtimer/ui/report/resources/TimerTaskSyncInfoEditor.properties +++ b/src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/TimerSyncEditor.properties @@ -41,6 +41,6 @@ testSyncFailureMessage = Test failed to synchronize, check the URL ? testSyncFailureTitle = Synchronization test failure syncURLHeader = URL -isActiveHeader = is Active -isWithAnnotationsHeader = Annotations +activeHeader = is Active +withAnnotationsHeader = Annotations lastSyncHeader = Last Synchronization \ No newline at end of file diff --git a/src/main/resources/org/chorem/jtimer/ui/report/resources/TimerTaskSyncInfoEditor_fr.properties b/src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/TimerSyncEditor_fr.properties similarity index 93% rename from src/main/resources/org/chorem/jtimer/ui/report/resources/TimerTaskSyncInfoEditor_fr.properties rename to src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/TimerSyncEditor_fr.properties index 3c5ee10..2140def 100644 --- a/src/main/resources/org/chorem/jtimer/ui/report/resources/TimerTaskSyncInfoEditor_fr.properties +++ b/src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/TimerSyncEditor_fr.properties @@ -39,7 +39,7 @@ testSyncSuccessTitle = Test r\u00E9ussi testSyncFailureMessage = Test \u00E9chou\u00E9, v\u00E9rifier l'URL ? testSyncFailureTitle = Echec du test de synchronisation -isActiveSync.Action.text = Synchronisation Active +active.Action.text = Synchronisation Active syncURLHeader = URL isActiveHeader = Active diff --git a/src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/dialog-close.png b/src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/dialog-close.png new file mode 100644 index 0000000..48ef771 Binary files /dev/null and b/src/main/resources/org/chorem/jtimer/plugin/timebundle/resources/dialog-close.png differ diff --git a/src/main/resources/org/chorem/jtimer/resources/JTimer.properties b/src/main/resources/org/chorem/jtimer/resources/JTimer.properties index e841b89..53bf3d7 100644 --- a/src/main/resources/org/chorem/jtimer/resources/JTimer.properties +++ b/src/main/resources/org/chorem/jtimer/resources/JTimer.properties @@ -62,9 +62,9 @@ editTask.Action.text = &Edit Task editTask.Action.accelerator = F2 editTask.Action.shortDescription = Edit task -updateTask.Action.text = &Edit Synchronization Info -updateTask.Action.accelerator = F5 -updateTask.Action.shortDescription = Edit Synchronization Info +editSync.Action.text = &Edit Synchronization +editSync.Action.accelerator = F5 +editSync.Action.shortDescription = Edit Synchronization closeTask.Action.text = &Open/Close Task closeTask.Action.accelerator = control O @@ -171,7 +171,6 @@ input.deleteTaskTitle=Confirm input.deleteTaskMessage=Do you want to delete task "%s" ? input.deleteTasksMessage=Do you want to delete the %d selected tasks ? input.mergeTaskMessage=Do you want to merge selected %d tasks\ninto "%s" ? -input.mergeTaskWithSyncInfoMessage = Do you want to merge selected %d tasks\ninto "%s" ? \nSynchronization Information will be lost ! input.mergeTaskTitle=Merge input.addAnnotationTitle=Add annotation input.addAnnotationMessage=Annotation for task "%s" : @@ -187,6 +186,7 @@ vetoable.common.merge.invalid.types=Can't merge project and task ! vetoable.common.move.invalid.types=Can't move project ! vetoable.saver.empty.name=Empty task name ! vetoable.saver.invalid.characters=Task name contains invalid characters ! +vetoable.timebundle.invalid.sync.list=Can't merge tasks with non matching sync urls ! # Start fail i18n startFail.title=Error diff --git a/src/main/resources/org/chorem/jtimer/resources/JTimer_fr.properties b/src/main/resources/org/chorem/jtimer/resources/JTimer_fr.properties index 0f3aa4b..c34b822 100644 --- a/src/main/resources/org/chorem/jtimer/resources/JTimer_fr.properties +++ b/src/main/resources/org/chorem/jtimer/resources/JTimer_fr.properties @@ -41,9 +41,8 @@ newTask.Action.shortDescription = Cr\u00E9ation d'une nouvelle t\u00E2che editTask.Action.text = \u00C9dition de la t\u00E2ch&e editTask.Action.shortDescription = \u00C9dition de la t\u00E2che - -updateTask.Action.text = &\u00C9dition de la synchronisation -updateTask.Action.shortDescription = Edition de la synchronisation +editSync.Action.text = &\u00C9dition de la synchronisation +editSync.Action.shortDescription = Edition de la synchronisation closeTask.Action.text = &Ouvrir/Fermer la t\u00E2che closeTask.Action.shortDescription = Ouvrir ou fermer la t\u00E2che @@ -135,7 +134,6 @@ input.deleteTaskTitle=Confirmation input.deleteTaskMessage=Voulez-vous supprimer la t\u00E2che "%s" ? input.deleteTasksMessage=Voulez-vous supprimer les %d t\u00E2ches s\u00E9lectionn\u00E9es ? input.mergeTaskMessage=Voulez-vous fusionner les %d t\u00E2ches s\u00E9lectionn\u00E9es\ndans la t\u00E2ches "%s" ? -input.mergeTaskWithSyncInfoMessage = Voulez-vous fusionner les %d t\u00E2ches s\u00E9lectionn\u00E9es\ndans la t\u00E2che "%s" ?\n Les informations de synchronisation seront perdues ! input.mergeTaskTitle=Fusionner input.addAnnotationTitle=Ajouter une annotation @@ -152,6 +150,7 @@ vetoable.common.merge.invalid.types=Impossible de fusionner un projet et une t\u vetoable.common.move.invalid.types=Impossible de d\u00E9placer un projet ! vetoable.saver.empty.name=Le nom est vide ! vetoable.saver.invalid.characters=Le nom contient des caract\u00E8res invalide ! +vetoable.timebundle.invalid.sync.list=Impossible de fusionner des t\u00E2ches n'ayant pas les m\u00EAmes adresses de synchronisation ! # Start fail i18n startFail.title=Erreur diff --git a/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor.properties b/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor.properties index 027a244..0f20572 100644 --- a/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor.properties +++ b/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor.properties @@ -29,5 +29,5 @@ cancel.Action.shortDescription = Revert changes and quit label.time.text = Time: label.annotation.text = Annotation: -label.syncURL.text = URL: +label.url.text = URL: label.title.text = Title: \ No newline at end of file diff --git a/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor_fr.properties b/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor_fr.properties index 9f6b39f..3b2c4e9 100644 --- a/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor_fr.properties +++ b/src/main/resources/org/chorem/jtimer/ui/resources/TimerTaskEditor_fr.properties @@ -29,5 +29,5 @@ cancel.Action.shortDescription = Annuler et quitter label.time.text = Temps: label.annotation.text = Note: -label.syncURL.text = URL: +label.url.text = URL: label.title.text = Titre: \ No newline at end of file diff --git a/src/site/rst/timebundle.rst b/src/site/rst/timebundle.rst new file mode 100644 index 0000000..7c3ce42 --- /dev/null +++ b/src/site/rst/timebundle.rst @@ -0,0 +1,30 @@ +.. - +.. * #%L +.. * jTimer +.. * %% +.. * Copyright (C) 2016 CodeLutin, Chatellier Eric +.. * %% +.. * This program is free software: you can redistribute it and/or modify +.. * it under the terms of the GNU General Public License as +.. * published by the Free Software Foundation, either version 3 of the +.. * License, or (at your option) any later version. +.. * +.. * This program is distributed in the hope that it will be useful, +.. * but WITHOUT ANY WARRANTY; without even the implied warranty of +.. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.. * GNU General Public License for more details. +.. * +.. * You should have received a copy of the GNU General Public +.. * License along with this program. If not, see +.. * <http://www.gnu.org/licenses/gpl-3.0.html>. +.. * #L% +.. - + +Time bundle +=========== + +Synchronization with time bundle is currently under developpement and disabled by default. + +To enable it, modify configuration with following line:: + + jtimer.io.synchronizer.class=org.chorem.jtimer.plugin.timebundle.TimeBundleSynchronizer diff --git a/src/site/site.xml b/src/site/site.xml index 5f40b98..00585dd 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -21,9 +21,9 @@ #L% --> <project name="${project.name}" - xmlns="http://maven.apache.org/DECORATION/1.6.0" + xmlns="http://maven.apache.org/DECORATION/1.7.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/DECORATION/1.6.0 http://maven.apache.org/xsd/decoration-1.6.0.xsd"> + xsi:schemaLocation="http://maven.apache.org/DECORATION/1.7.0 http://maven.apache.org/xsd/decoration-1.7.0.xsd"> <custom> <fluidoSkin> @@ -89,6 +89,7 @@ <item name="Swing framework" href="devel/saf.html"/> <item name="File format" href="devel/fileformat.html"/> </item> + <item name="Time bundle" href="timebundle.html"/> </menu> <menu ref="reports"/> diff --git a/src/test/java/org/chorem/jtimer/AbstractJTimerTest.java b/src/test/java/org/chorem/jtimer/AbstractJTimerTest.java index 4cfc75c..551be0c 100644 --- a/src/test/java/org/chorem/jtimer/AbstractJTimerTest.java +++ b/src/test/java/org/chorem/jtimer/AbstractJTimerTest.java @@ -105,6 +105,8 @@ public abstract class AbstractJTimerTest { // copy .svn folders FileUtils.copyDirectory(new File("src/test/resources/testdata"), new File(testDataDirectory, "data"), HiddenFileFilter.VISIBLE); + FileUtils.copyDirectory(new File("src/test/resources/testsync"), + new File(testDataDirectory, "timebundle"), HiddenFileFilter.VISIBLE); // force null, to force new instance JTimerFactory.saver = null; diff --git a/src/test/java/org/chorem/jtimer/entities/TimerTaskHelperTest.java b/src/test/java/org/chorem/jtimer/entities/TimerTaskHelperTest.java index a65d238..88eb68f 100644 --- a/src/test/java/org/chorem/jtimer/entities/TimerTaskHelperTest.java +++ b/src/test/java/org/chorem/jtimer/entities/TimerTaskHelperTest.java @@ -21,18 +21,12 @@ */ package org.chorem.jtimer.entities; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import org.apache.commons.lang3.time.DateUtils; import org.chorem.jtimer.AbstractJTimerTest; import org.testng.Assert; import org.testng.annotations.Test; +import java.util.List; + /** * Test related to TimerTaskHelper. */ @@ -55,56 +49,4 @@ public class TimerTaskHelperTest extends AbstractJTimerTest { Assert.assertEquals(components.get(1), task1); Assert.assertEquals(components.get(2), task2); } - - /** - * Test json production - */ - @Test - public void taskToJSONFormatTest() { - TimerTask task = new TimerTask(); - task.setName("JsonBuilder Test"); - SyncInfo sync = new SyncInfo("http://localhost:3000"); - task.addSyncInfo(sync); - //set the creation date at same date as first timing date (to fit test object) - task.setCreationDate(new Date(1462831200000L)); - sync.setLastSync(new Date(1462831200000L)); - //date : 2016-05-10 - task.setTime(new Date(1462831200000L), 452000L); - //date : 2016-05-12 - task.setTime(new Date(1463004000000L), 4533000L); - - - //create json object - JsonObject objectToHave = new JsonObject(); - JsonArray periodArray = new JsonArray(); - JsonObject periodElement1 = new JsonObject(); - //id is date+_+task.number, here -1 - periodElement1.addProperty("id", "2016-05-10_-1"); - periodElement1.addProperty("startDate", "2016-05-10T00:00:00+01:00"); - periodElement1.addProperty("duration", 452); - periodArray.add(periodElement1); - JsonObject periodElement2 = new JsonObject(); - periodElement2.addProperty("id", "2016-05-12_-1"); - periodElement2.addProperty("startDate", "2016-05-12T00:00:00+01:00"); - periodElement2.addProperty("duration", 4533); - periodArray.add(periodElement2); - - objectToHave.addProperty("URL", "http://localhost:3000"); - //startDate corresponds to lastSync in taksToJsonObject - objectToHave.addProperty("startDate", "2016-05-10T00:00:00+01:00"); - //endDate is nextday (from currenttime) - Date endDate = DateUtils.ceiling(new Date(), Calendar.DAY_OF_MONTH); - String timestamp = "T00:00:00+01:00"; - LocalDate endPeriodDate = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); - String endPeriodString = endPeriodDate.toString() + timestamp; - - objectToHave.addProperty("endDate", endPeriodString); - objectToHave.add("periods", periodArray); - - - //make a list of json objects from the task - JsonObject jsonObject = TimerTaskHelper.taskToJsonObject(task, sync, "+01:00"); - //compare it - Assert.assertEquals(jsonObject, objectToHave); - } } diff --git a/src/test/java/org/chorem/jtimer/entities/TimerTaskTest.java b/src/test/java/org/chorem/jtimer/entities/TimerTaskTest.java index 4d809cb..87cc0e1 100644 --- a/src/test/java/org/chorem/jtimer/entities/TimerTaskTest.java +++ b/src/test/java/org/chorem/jtimer/entities/TimerTaskTest.java @@ -116,46 +116,4 @@ public class TimerTaskTest extends AbstractJTimerTest { Assert.assertEquals(task.getSubTasks().size(), 1); Assert.assertEquals(clonedTask.getSubTasks().size(), 0); } - - /** - * Test that no URL is present twice for a task - */ - @Test - public void addSyncInfoTest() { - TimerTask task = new TimerTask(); - String url1 = "localhost/test"; - String url2 = "localhost/test"; - - task.addSyncInfo(url1); - task.addSyncInfo(url2); - - Assert.assertEquals(task.getSynchronizingInfoList().size(), 1); - - String url3 = "localhost"; - task.addSyncInfo(url3); - - Assert.assertEquals(task.getSynchronizingInfoList().size(), 2); - } - - /** - * Test that removal of URL works ? - */ - @Test - public void removeSyncInfoTest() { - TimerTask task = new TimerTask(); - - SyncInfo info1 = new SyncInfo("localhost/test"); - SyncInfo info2 = new SyncInfo("localhost/other"); - - task.addSyncInfo(info1); - task.addSyncInfo(info2); - - Assert.assertTrue(task.getSynchronizingInfoList().size() == 2); - - task.removeSyncInfo(info1); - - Assert.assertTrue(task.getSynchronizingInfoList().size() == 1); - Assert.assertTrue(task.getSynchronizingInfoList().contains(info2)); - - } } diff --git a/src/test/java/org/chorem/jtimer/io/AbstractSaverTest.java b/src/test/java/org/chorem/jtimer/io/AbstractSaverTest.java index ddc0d96..c387b13 100644 --- a/src/test/java/org/chorem/jtimer/io/AbstractSaverTest.java +++ b/src/test/java/org/chorem/jtimer/io/AbstractSaverTest.java @@ -52,7 +52,7 @@ public class AbstractSaverTest extends AbstractJTimerTest { File file = File.createTempFile("test", ".test"); AbstractSaver saver = new GTimerIncrementalSaver(); - File backupFile = saver.makeBackupFile(file); + File backupFile = BackupUtils.makeBackupFile(file); Assert.assertNotNull(backupFile); Assert.assertTrue(backupFile.isFile()); @@ -69,7 +69,7 @@ public class AbstractSaverTest extends AbstractJTimerTest { FileUtils.writeStringToFile(file, "oldcontent", StandardCharsets.UTF_8); AbstractSaver saver = new GTimerIncrementalSaver(); - File backupFile = saver.makeBackupFile(file); + File backupFile = BackupUtils.makeBackupFile(file); Assert.assertNotNull(backupFile); Assert.assertTrue(backupFile.isFile()); @@ -78,7 +78,7 @@ public class AbstractSaverTest extends AbstractJTimerTest { Assert.assertEquals(FileUtils.readFileToString(file, StandardCharsets.UTF_8), "newcontent"); Assert.assertEquals(FileUtils.readFileToString(backupFile, StandardCharsets.UTF_8), "oldcontent"); - boolean result = saver.restoreBackupFile(backupFile); + boolean result = BackupUtils.restoreBackupFile(backupFile); Assert.assertTrue(result); Assert.assertFalse(backupFile.isFile()); @@ -95,9 +95,9 @@ public class AbstractSaverTest extends AbstractJTimerTest { File file = File.createTempFile("test", ".test"); AbstractSaver saver = new GTimerIncrementalSaver(); - File backupFile = saver.makeBackupFile(file); - - saver.deleteBackupFile(backupFile); + File backupFile = BackupUtils.makeBackupFile(file); + + BackupUtils.deleteBackupFile(backupFile); Assert.assertFalse(backupFile.isFile()); } diff --git a/src/test/java/org/chorem/jtimer/io/GTimerIncrementalSaverTest.java b/src/test/java/org/chorem/jtimer/io/GTimerIncrementalSaverTest.java index 6dc4d8f..a4c8a19 100644 --- a/src/test/java/org/chorem/jtimer/io/GTimerIncrementalSaverTest.java +++ b/src/test/java/org/chorem/jtimer/io/GTimerIncrementalSaverTest.java @@ -36,7 +36,7 @@ import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.io.FileUtils; import org.chorem.jtimer.AbstractJTimerTest; -import org.chorem.jtimer.entities.SyncInfo; +import org.chorem.jtimer.entities.TimerSync; import org.chorem.jtimer.entities.TimerAlert; import org.chorem.jtimer.entities.TimerProject; import org.chorem.jtimer.entities.TimerTask; @@ -417,25 +417,4 @@ public class GTimerIncrementalSaverTest extends AbstractJTimerTest { } }*/ } - - /** - * Test that synchronization Info is properly parsed - * @throws IOException - */ - @Test - public void parseSyncInfoTest() throws IOException{ - - GTimerIncrementalSaver gsaver = (GTimerIncrementalSaver) testSaver; - - TimerTask task = new TimerTask(); - task.setNumber(41); - - gsaver.parseSyncInfo(task); - - SyncInfo syncInfo = new SyncInfo("http://localhost:3000"); - - Assert.assertNotNull(task.getSynchronizingInfoList()); - Assert.assertTrue(task.getSynchronizingInfoList().contains(syncInfo)); - - } } diff --git a/src/test/java/org/chorem/jtimer/plugin/timebundle/TImeBundleSaverTest.java b/src/test/java/org/chorem/jtimer/plugin/timebundle/TImeBundleSaverTest.java new file mode 100644 index 0000000..4dac2cf --- /dev/null +++ b/src/test/java/org/chorem/jtimer/plugin/timebundle/TImeBundleSaverTest.java @@ -0,0 +1,45 @@ +package org.chorem.jtimer.plugin.timebundle; + +import org.chorem.jtimer.AbstractJTimerTest; +import org.chorem.jtimer.entities.TimerProject; +import org.chorem.jtimer.entities.TimerTask; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.Collections; + +/** + * Created by chatellier on 02/08/16. + */ +public class TImeBundleSaverTest extends AbstractJTimerTest { + + protected TimeBundleSaver saver; + + @BeforeMethod + @Override + public void beforeTest() throws IOException { + super.beforeTest(); + saver = new TimeBundleSaver(); + } + + /** + * Test that synchronization Info is properly parsed + * @throws IOException + */ + @Test + public void parseSyncInfoTest() throws IOException{ + + TimerTask task = new TimerTask(); + task.setNumber(41); + TimerProject project = new TimerProject(); + project.setNumber(1); + project.addTask(task); + saver.dataLoaded(Collections.singletonList(project)); + + Assert.assertNotNull(task.getSyncs()); + Assert.assertEquals(task.getSyncs().get(0).getUrl(), "http://localhost:3000"); + + } +} diff --git a/src/test/java/org/chorem/jtimer/entities/TimerTaskHelperTest.java b/src/test/java/org/chorem/jtimer/plugin/timebundle/TimeBundleHelperTest.java similarity index 78% copy from src/test/java/org/chorem/jtimer/entities/TimerTaskHelperTest.java copy to src/test/java/org/chorem/jtimer/plugin/timebundle/TimeBundleHelperTest.java index a65d238..3bdb517 100644 --- a/src/test/java/org/chorem/jtimer/entities/TimerTaskHelperTest.java +++ b/src/test/java/org/chorem/jtimer/plugin/timebundle/TimeBundleHelperTest.java @@ -19,42 +19,26 @@ * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ -package org.chorem.jtimer.entities; +package org.chorem.jtimer.plugin.timebundle; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.Calendar; -import java.util.Date; -import java.util.List; import org.apache.commons.lang3.time.DateUtils; import org.chorem.jtimer.AbstractJTimerTest; +import org.chorem.jtimer.entities.TimerSync; +import org.chorem.jtimer.entities.TimerTask; import org.testng.Assert; import org.testng.annotations.Test; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Date; + /** - * Test related to TimerTaskHelper. + * Test related to TimeBundleHelper. */ -public class TimerTaskHelperTest extends AbstractJTimerTest { - - /** - * Test get path from parent. - */ - @Test - public void getPathFromParentTest() { - TimerProject project = new TimerProject(); - TimerTask task1 = new TimerTask(); - TimerTask task2 = new TimerTask(); - - project.addTask(task1); - task1.addTask(task2); - - List<TimerTask> components = TimerTaskHelper.getPathFromParent(task2); - Assert.assertEquals(components.get(0), project); - Assert.assertEquals(components.get(1), task1); - Assert.assertEquals(components.get(2), task2); - } +public class TimeBundleHelperTest extends AbstractJTimerTest { /** * Test json production @@ -63,8 +47,8 @@ public class TimerTaskHelperTest extends AbstractJTimerTest { public void taskToJSONFormatTest() { TimerTask task = new TimerTask(); task.setName("JsonBuilder Test"); - SyncInfo sync = new SyncInfo("http://localhost:3000"); - task.addSyncInfo(sync); + TimerSync sync = new TimerSync("http://localhost:3000"); + task.addSync(sync); //set the creation date at same date as first timing date (to fit test object) task.setCreationDate(new Date(1462831200000L)); sync.setLastSync(new Date(1462831200000L)); @@ -103,7 +87,7 @@ public class TimerTaskHelperTest extends AbstractJTimerTest { //make a list of json objects from the task - JsonObject jsonObject = TimerTaskHelper.taskToJsonObject(task, sync, "+01:00"); + JsonObject jsonObject = TimeBundleHelper.taskToJsonObject(task, sync); //compare it Assert.assertEquals(jsonObject, objectToHave); } diff --git a/src/test/resources/testdata/41.task.sync b/src/test/resources/testdata/41.task.sync deleted file mode 100644 index 759598c..0000000 --- a/src/test/resources/testdata/41.task.sync +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "syncURL": "http://localhost:3000", - "lastSync": "Jan 1, 1970 1:00:00 AM", - "isActiveSync": true, - "isWithAnnotations": false - } -] \ No newline at end of file diff --git a/src/test/resources/testsync/41.task.sync b/src/test/resources/testsync/41.task.sync new file mode 100644 index 0000000..3a085bf --- /dev/null +++ b/src/test/resources/testsync/41.task.sync @@ -0,0 +1,8 @@ +[ + { + "url": "http://localhost:3000", + "lastSync": "Jan 1, 1970 1:00:00 AM", + "active": true, + "withAnnotations": false + } +] \ No newline at end of file -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.