package de.saring.sportstracker.gui; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import de.saring.sportstracker.core.STException; import de.saring.sportstracker.core.STExceptionID; import de.saring.sportstracker.core.STOptions; import de.saring.sportstracker.data.EntryFilter; import de.saring.sportstracker.data.EntryList; import de.saring.sportstracker.data.Exercise; import de.saring.sportstracker.data.ExerciseList; import de.saring.sportstracker.data.Note; import de.saring.sportstracker.data.NoteList; import de.saring.sportstracker.data.SportTypeList; import de.saring.sportstracker.data.Weight; import de.saring.sportstracker.data.WeightList; import de.saring.sportstracker.storage.IStorage; import de.saring.util.XmlBeanStorage; import de.saring.util.data.IdObject; import de.saring.util.data.IdObjectListChangeListener; /** * This class contains all document (MVC) related data and functionality of the * SportsTracker application. * * @author Stefan Saring */ @Singleton public class STDocumentImpl implements STDocument { private static final Logger LOGGER = Logger.getLogger(STDocumentImpl.class.getName()); private static final String FILENAME_SPORT_TYPE_LIST = "sport-types.xml"; private static final String FILENAME_EXERCISE_LIST = "exercises.xml"; private static final String FILENAME_NOTE_LIST = "notes.xml"; private static final String FILENAME_WEIGHT_LIST = "weights.xml"; private static final String FILENAME_OPTIONS = "st-options.xml"; private final STContext context; /** * The sport type list of the user. */ private SportTypeList sportTypeList; /** * This list contains all the exercises of the user. For displaying the * list in GUI the method GetFilterableExerciseList() should be used instead. */ private ExerciseList exerciseList; /** * The note entry list of the user. */ private NoteList noteList; /** * The body weight entry list of the user. */ private WeightList weightList; /** * The data storage instance of the application. */ private final IStorage storage; /** * The directory where the application data of the user is stored. */ private String dataDirectory; /** This flag is true when data has been modified but not saved yet. */ private boolean dirtyData; /** * The application settings. */ private STOptions options; /** * This flag is true when the view displays only exercises for the current filter. */ private boolean filterEnabled; /** * The current exercise filter in the view. */ private EntryFilter currentFilter; /** * Standard c'tor. * * @param context the SportsTracker context * @param storage the data storage instance to be used */ @Inject public STDocumentImpl(final STContext context, final IStorage storage) { this.context = context; this.storage = storage; // create name of directory where the data is stored dataDirectory = System.getProperty("user.home") + "/.sportstracker"; // start with empty lists sportTypeList = new SportTypeList(); exerciseList = new ExerciseList(); noteList = new NoteList(); weightList = new WeightList(); dirtyData = false; // create default filter for current month, but it is disabled filterEnabled = false; currentFilter = EntryFilter.createDefaultExerciseFilter(); } @Override public SportTypeList getSportTypeList() { return sportTypeList; } @Override public ExerciseList getExerciseList() { return exerciseList; } @Override public NoteList getNoteList() { return noteList; } @Override public WeightList getWeightList() { return weightList; } @Override public STOptions getOptions() { return options; } @Override public boolean isDirtyData() { return dirtyData; } @Override public boolean isFilterEnabled() { return filterEnabled; } @Override public void setFilterEnabled(boolean filterEnabled) { this.filterEnabled = filterEnabled; } @Override public EntryFilter getCurrentFilter() { return currentFilter; } @Override public void setCurrentFilter(EntryFilter currentFilter) { this.currentFilter = currentFilter; } @Override public String getDataDirectory() { return dataDirectory; } @Override public void evaluateCommandLineParameters(final List<String> parameters) { // check for a custom data directory (optional) for (String parameter : parameters) { if (parameter.startsWith(PARAMETER_DATA_DIR)) { final String dataDir = parameter.substring(PARAMETER_DATA_DIR.length()).trim(); if (dataDir.length() > 0) { dataDirectory = dataDir; } } } } @Override public void loadOptions() { final String optionsPath = dataDirectory + File.separator + FILENAME_OPTIONS; if (Files.exists(Paths.get(optionsPath))) { LOGGER.info("Loading application options..."); try { options = (STOptions) XmlBeanStorage.loadBean(optionsPath); } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to load application options from '" + optionsPath + "', using default values ...", e); } } // use default options at first start or on load errors if (options == null) { LOGGER.log(Level.WARNING, "Using default application options..."); options = new STOptions(); } } @Override public void storeOptions() { LOGGER.info("Storing application options..."); final String optionsPath = dataDirectory + File.separator + FILENAME_OPTIONS; try { XmlBeanStorage.saveBean(options, optionsPath); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to store application options to '" + optionsPath + "' ...", e); } } @Override public void createApplicationDirectory() throws STException { File fDataDirectory = new File(dataDirectory); if (!fDataDirectory.exists() || !fDataDirectory.isDirectory()) { if (!fDataDirectory.mkdir()) { throw new STException(STExceptionID.DOCUMENT_CREATE_APP_DIRECTORY, "Failed to create application data directory '" + dataDirectory + "' ..."); } } } @Override public EntryList<Exercise> getFilterableExerciseList() { if ((filterEnabled) && (currentFilter != null)) { // use current filter to get list return exerciseList.getEntriesForFilter(currentFilter); } else { // no filter: return list of all exercises return exerciseList; } } @Override public EntryList<Note> getFilterableNoteList() { if ((filterEnabled) && (currentFilter != null)) { // use current filter to get list return noteList.getEntriesForFilter(currentFilter); } else { // no filter: return list of all notes return noteList; } } @Override public EntryList<Weight> getFilterableWeightList() { if ((filterEnabled) && (currentFilter != null)) { // use current filter to get list return weightList.getEntriesForFilter(currentFilter); } else { // no filter: return list of all weights return weightList; } } @Override public void readApplicationData() throws STException { try { // read application data from XML files sportTypeList = storage.readSportTypeList(dataDirectory + "/" + FILENAME_SPORT_TYPE_LIST); exerciseList = storage.readExerciseList(dataDirectory + "/" + FILENAME_EXERCISE_LIST, sportTypeList); noteList = storage.readNoteList(dataDirectory + "/" + FILENAME_NOTE_LIST); weightList = storage.readWeightList(dataDirectory + "/" + FILENAME_WEIGHT_LIST); } finally { // register this document as a listener for list content changes // (also when reading data has failed) registerListChangeListener(this); dirtyData = false; } } @Override public void storeApplicationData() throws STException { // store application data in XML files storage.storeSportTypeList(sportTypeList, dataDirectory + "/" + FILENAME_SPORT_TYPE_LIST); storage.storeExerciseList(exerciseList, dataDirectory + "/" + FILENAME_EXERCISE_LIST); storage.storeNoteList(noteList, dataDirectory + "/" + FILENAME_NOTE_LIST); storage.storeWeightList(weightList, dataDirectory + "/" + FILENAME_WEIGHT_LIST); dirtyData = false; } @Override public List<Exercise> checkExerciseFiles() { return exerciseList.stream() .filter(exercise -> exercise.getHrmFile() != null && !new File(exercise.getHrmFile()).exists()) .collect(Collectors.toList()); } @Override public void listChanged(IdObject changedObject) { // one of the data lists has been changed => set dirty data flag dirtyData = true; } @Override public void registerListChangeListener(IdObjectListChangeListener listener) { sportTypeList.addListChangeListener(listener); exerciseList.addListChangeListener(listener); noteList.addListChangeListener(listener); weightList.addListChangeListener(listener); } }