/* * jMemorize - Learning made easy (and fun) - A Leitner flashcards tool * Copyright(C) 2004-2008 Riad Djemili and contributors * * 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 1, 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package jmemorize.core; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.nio.channels.FileChannel; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Observable; import java.util.Properties; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import java.util.prefs.Preferences; import jmemorize.core.io.XmlBuilder; import jmemorize.core.learn.DefaultLearnSession; import jmemorize.core.learn.LearnHistory; import jmemorize.core.learn.LearnSession; import jmemorize.core.learn.LearnSessionObserver; import jmemorize.core.learn.LearnSessionProvider; import jmemorize.core.learn.LearnSettings; import jmemorize.gui.swing.frames.MainFrame; import jmemorize.util.RecentItems; /** * The main class of the application. * * @author djemili */ public class Main extends Observable implements LearnSessionProvider, LessonProvider, CategoryObserver { public interface ProgramEndObserver { /** * This method is notified when the program ends. */ public void onProgramEnd(); } public static final Properties PROPERTIES = new Properties(); public static final Preferences USER_PREFS = Preferences.userRoot().node("de/riad/jmemorize"); //$NON-NLS-1$ private static final String PROPERTIES_PATH = "/resource/jMemorize.properties"; //$NON-NLS-1$ public static final File STATS_FILE = new File(System.getProperty("user.home")+"/.jmemorize-stats.xml"); //$NON-NLS-1$ //$NON-NLS-2$ private RecentItems m_recentFiles = new RecentItems(5, USER_PREFS.node("recent.files")); //$NON-NLS-1$ private static Main m_instance; private MainFrame m_frame; private Lesson m_lesson; private LearnSettings m_learnSettings; private LearnHistory m_globalLearnHistory; private int m_runningSessions = 0; // observers private List<LessonObserver> m_lessonObservers = new LinkedList<LessonObserver>(); private List<LearnSessionObserver> m_learnSessionObservers = new LinkedList<LearnSessionObserver>(); private List<ProgramEndObserver> m_programEndObservers = new LinkedList<ProgramEndObserver>(); // simple logging support private static final Logger logger = Logger.getLogger("jmemorize"); private static Throwable m_lastLoggedThrowable; /** * @return the singleton instance of Main. */ public static Main getInstance() { if (m_instance == null) { m_instance = new Main(); } return m_instance; } public static Date getNow() { return new Date(); } public static Date getTomorrow() { return new Date(new Date().getTime() + Card.ONE_DAY); } /* (non-Javadoc) * Declared in jmemorize.core.LessonProvider */ public void createNewLesson() { ImageRepository.getInstance().clear(); setLesson(new Lesson(false)); } /* (non-Javadoc) * @see jmemorize.core.LessonProvider */ public void setLesson(Lesson lesson) { Lesson oldLesson = m_lesson; m_lesson = lesson; if (oldLesson != null) { fireLessonClosed(oldLesson); } if (m_frame != null) // TODO remove call { m_frame.setLesson(m_lesson); } fireLessonLoaded(m_lesson); } /* (non-Javadoc) * Declared in jmemorize.core.LessonProvider */ public void loadLesson(File file) throws IOException { try { ImageRepository.getInstance().clear(); Lesson lesson = new Lesson(false); XmlBuilder.loadFromXMLFile(file, lesson); lesson.setFile(file); lesson.setCanSave(false); m_recentFiles.push(file.getAbsolutePath()); setLesson(lesson); //startExpirationTimer(); TODO expiration timer } catch (Exception e) { m_recentFiles.remove(file.getAbsolutePath()); logThrowable("Error loading lesson", e); throw new IOException(e.getMessage()); } } /* (non-Javadoc) * Declared in jmemorize.core.LessonProvider */ public void saveLesson(Lesson lesson, File file) throws IOException { try { File tempFile = new File(file.getAbsolutePath()+"~"); //$NON-NLS-1$ XmlBuilder.saveAsXMLFile(tempFile, lesson); file.delete(); copyFile(tempFile, file); lesson.setFile(file); // note: sets file only if no exception lesson.setCanSave(false); m_recentFiles.push(file.getAbsolutePath()); for (LessonObserver observer : m_lessonObservers) { observer.lessonSaved(lesson); } } catch (Throwable t) { throw new IOException(t.getMessage()); } } /* (non-Javadoc) * Declared in jmemorize.core.LessonProvider */ public Lesson getLesson() { return m_lesson; } /* (non-Javadoc) * Declared in jmemorize.core.LessonProvider */ public RecentItems getRecentLessonFiles() { return m_recentFiles; } /* (non-Javadoc) * @see jmemorize.core.LessonProvider */ public void addLessonObserver(LessonObserver observer) { m_lessonObservers.add(observer); } /* (non-Javadoc) * @see jmemorize.core.LessonProvider */ public void removeLessonObserver(LessonObserver observer) { m_lessonObservers.remove(observer); } /** * Adds a ProgramEndObserver that will be fired when this program closes. * * @param observer */ public void addProgramEndObserver(ProgramEndObserver observer) { m_programEndObservers.add(observer); } /** * @see #addProgramEndObserver(jmemorize.core.Main.ProgramEndObserver) */ public void removeProgramEndObserver(ProgramEndObserver observer) { m_programEndObservers.remove(observer); } /** * Notifies all program end observers and exists the application. */ public void exit() { for (ProgramEndObserver observer : m_programEndObservers) { observer.onProgramEnd(); } System.exit(0); } /* (non-Javadoc) * Declared in jmemorize.core.LearnSessionProvider */ public void startLearnSession(LearnSettings settings, List<Card> selectedCards, Category category,boolean learnUnlearned, boolean learnExpired) { LearnSession session = new DefaultLearnSession(category, settings, selectedCards, learnUnlearned, learnExpired, this); m_runningSessions++; for (LearnSessionObserver observer : m_learnSessionObservers) { observer.sessionStarted(session); } // this needs to be called after notifying the observers so that they // don't miss the first card session.startLearning(); } /* (non-Javadoc) * Declared in jmemorize.core.LearnSessionProvider */ public void sessionEnded(LearnSession session) { m_runningSessions--; if (session.isRelevant()) { LearnHistory history = m_lesson.getLearnHistory(); history.addSummary( session.getStart(), session.getEnd(), session.getPassedCards().size(), session.getFailedCards().size(), session.getSkippedCards().size(), session.getRelearnedCards().size()); } for (LearnSessionObserver observer : m_learnSessionObservers) { observer.sessionEnded(session); } } /* (non-Javadoc) * Declared in jmemorize.core.LearnSessionProvider */ public boolean isSessionRunning() { return m_runningSessions > 0; } /* (non-Javadoc) * Declared in jmemorize.core.LearnSessionProvider */ public void addLearnSessionObserver(LearnSessionObserver observer) { m_learnSessionObservers.add(observer); } /* (non-Javadoc) * Declared in jmemorize.core.LearnSessionProvider */ public void removeLearnSessionObserver(LearnSessionObserver observer) { m_learnSessionObservers.remove(observer); } /** * @return the main frame. */ public MainFrame getFrame() { return m_frame; } /** * @return currently loaded learn strategy. */ public LearnSettings getLearnSettings() { return m_learnSettings; } /** * @return the statistics for jMemorize. */ public LearnHistory getGlobalLearnHistory() { return m_globalLearnHistory; } /* (non-Javadoc) * Declared in jmemorize.core.CategoryObserver */ public void onCardEvent(int type, Card card, Category category, int deck) { fireLessonModified(m_lesson); } /* (non-Javadoc) * Declared in jmemorize.core.CategoryObserver */ public void onCategoryEvent(int type, Category category) { fireLessonModified(m_lesson); } public Main() { InputStream propertyStream = null; try { // TODO - make this adjustable // Note that the limit might not be enough for finer. Handler fh = new FileHandler("%t/jmemorize%g.log", 10000, 3); fh.setLevel(Level.WARNING); fh.setFormatter(new SimpleFormatter()); logger.addHandler(fh); URL resource = getClass().getResource(PROPERTIES_PATH); // PROPERTIES.load(resource.openStream()); if (resource != null) { propertyStream = resource.openStream(); PROPERTIES.load(propertyStream); } } catch (Exception e) { e.printStackTrace(); logThrowable("Initialization problem", e); } finally { try { if (propertyStream != null) propertyStream.close(); } catch (IOException e) { e.printStackTrace(); logThrowable("Initialization problem", e); } } } /** * @return <code>true</code> if this is the devel version running. * <code>false</code> if it is the release version. This can be used for * new and expiremental features. */ public static boolean isDevel() { String property = PROPERTIES.getProperty("project.release"); //$NON-NLS-1$ return !Boolean.valueOf(property).booleanValue(); } /* * Logging utilities */ public static Logger getLogger() { return logger; } // note that we cache the throwable so that we only log it the first time. // This allows us to put a catch all call to this function in ErrorDialog. // Ideally, exceptions should be logged where they are first caught, because // we have more information about the exception there. public static void logThrowable(String msg, Throwable t) { if (t != null && m_lastLoggedThrowable != t) { m_lastLoggedThrowable = t; logger.severe(msg); // TODO, consider writing these to the log file only once? String java = System.getProperty("java.version"); String os = System.getProperty("os.name"); String version = Main.PROPERTIES.getProperty("project.version"); String buildId = Main.PROPERTIES.getProperty("buildId"); String txt = "Ver "+ version +" ("+ buildId +") - Java "+ java +" , OS "+ os; logger.severe(txt); StringWriter strWriter = new StringWriter(); PrintWriter prWriter = new PrintWriter(strWriter); t.printStackTrace(prWriter); logger.severe(strWriter.toString()); } } public static void clearLastThrowable() { m_lastLoggedThrowable = null; } private static void copyFile(File in, File out) throws IOException { FileChannel sourceChannel = null; FileChannel destinationChannel = null; try { sourceChannel = new FileInputStream(in).getChannel(); destinationChannel = new FileOutputStream(out).getChannel(); sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel); } finally { if (sourceChannel != null) sourceChannel.close(); if (destinationChannel != null) destinationChannel.close(); } } private void run(File file) { createNewLesson(); startStats(); m_frame = new MainFrame(); m_learnSettings = Settings.loadStrategy(m_frame); m_frame.setVisible(true); if (file != null) { m_frame.loadLesson(file); } } private void startStats() { m_globalLearnHistory = new LearnHistory(STATS_FILE); } private void fireLessonLoaded(Lesson lesson) { lesson.getRootCategory().addObserver(this); for (LessonObserver observer : m_lessonObservers) { observer.lessonLoaded(lesson); } } private void fireLessonClosed(Lesson lesson) { lesson.getRootCategory().removeObserver(this); for (LessonObserver observer : m_lessonObservers) { observer.lessonClosed(lesson); } } private void fireLessonModified(Lesson lesson) { if (lesson.canSave()) { for (LessonObserver observer : m_lessonObservers) { observer.lessonModified(lesson); } } } /** * @param args the command line arguments */ public static void main(String args[]) { File file = args.length >= 1 ? new File(args[0]) : null; Main.getInstance().run(file); } }