/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * 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 2 * of the License, or 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.services.bookmark; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.text.SimpleDateFormat; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Vector; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.jajuk.base.Collection; import org.jajuk.base.FileManager; import org.jajuk.events.HighPriorityObserver; import org.jajuk.events.JajukEvent; import org.jajuk.events.JajukEvents; import org.jajuk.events.ObservationManager; import org.jajuk.services.core.PersistenceService; import org.jajuk.services.core.SessionService; import org.jajuk.ui.widgets.InformationJPanel; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.Messages; import org.jajuk.util.UtilString; import org.jajuk.util.UtilSystem; import org.jajuk.util.error.JajukException; import org.jajuk.util.log.Log; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; /** * Stores all files user read History is used as a model for some Swing * components, so any changes on the model should be done in the EDT see * http://java * .sun.com/javase/6/docs/api/javax/swing/package-summary.html#threading */ public final class History extends DefaultHandler implements HighPriorityObserver { /** Self instance. */ private static History history = new History(); /** History repository, last play first. KEEP THIS A VECTOR, not an ArrayList, * it is accessed directly as model for the SearchJPanel*/ private static Vector<HistoryItem> items = new Vector<HistoryItem>(100); /** History begin date. */ private static long lDateStart; /** Cached date formatter. */ private SimpleDateFormat formatter; /** * Instance getter. * * @return the instance */ public static History getInstance() { return history; } /** * Hidden constructor. */ private History() { super(); ObservationManager.register(this); // check if something has already started if (ObservationManager.getDetailLastOccurence(JajukEvents.FILE_LAUNCHED, Const.DETAIL_CURRENT_FILE_ID) != null && ObservationManager.getDetailLastOccurence(JajukEvents.FILE_LAUNCHED, Const.DETAIL_CURRENT_DATE) != null) { update(new JajukEvent(JajukEvents.FILE_LAUNCHED, ObservationManager.getDetailsLastOccurence(JajukEvents.FILE_LAUNCHED))); } // Fill date formatter formatter = new SimpleDateFormat(Messages.getString("HistoryItem.0"), Locale.getDefault()); } /* * (non-Javadoc) * * @see org.jajuk.events.Observer#getRegistrationKeys() */ @Override public Set<JajukEvents> getRegistrationKeys() { Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>(); eventSubjectSet.add(JajukEvents.FILE_LAUNCHED); eventSubjectSet.add(JajukEvents.DEVICE_REFRESH); eventSubjectSet.add(JajukEvents.CLEAR_HISTORY); eventSubjectSet.add(JajukEvents.FILE_NAME_CHANGED); eventSubjectSet.add(JajukEvents.LANGUAGE_CHANGED); return eventSubjectSet; } /** * Gets the history, newest played tracks first. * <br/>Do not return a defensive copy as the history combobox relies on it as model * * @return the history */ public Vector<HistoryItem> getItems() { return items; } /** * Add an history item. * * @param sFileId * @param lDate */ public void addItem(String sFileId, long lDate) { // no history if (Conf.getString(Const.CONF_HISTORY).equals("0")) { return; } // check the ID maps an existing file if (FileManager.getInstance().getFileByID(sFileId) == null) { return; } // OK, begin to add the new history item HistoryItem hi = new HistoryItem(sFileId, lDate); // check if previous history item is not the same, // otherwise, keep last one if (items.size() > 0) { HistoryItem hiPrevious = items.get(0); if (hiPrevious.getFileId().equals(hi.getFileId())) { items.remove(0); } items.add(0, hi); // keep only most recent date // test maximum history size, if >, remove oldest item if (items.size() > Const.MAX_HISTORY_SIZE) { items.remove(items.size() - 1); } } else { // first element in history items.add(0, hi); } PersistenceService.getInstance().setHistoryChanged(); } /** * Clear history. */ public void clear() { items.clear(); } /** * Cleanup history of dead items (removed files after a refresh). */ public void cleanup() { Iterator<HistoryItem> it = items.iterator(); while (it.hasNext()) { HistoryItem hi = it.next(); if (FileManager.getInstance().getFileByID(hi.getFileId()) == null) { it.remove(); } } } /** * Change ID for a file. * * @param sIDOld * @param sIDNew */ public void changeID(final String sIDOld, final String sIDNew) { for (int i = 0; i < items.size(); i++) { HistoryItem hi = items.get(i); if (hi.getFileId().equals(sIDOld)) { items.remove(i); items.add(i, new HistoryItem(sIDNew, hi.getDate())); } } } /** * Clear history for all history items before iDays days. * * @param iDays */ public void clear(final int iDays) { // Begins by clearing deleted files Iterator<HistoryItem> it = items.iterator(); while (it.hasNext()) { HistoryItem hi = it.next(); if (FileManager.getInstance().getFileByID(hi.getFileId()) == null) { it.remove(); } } // Follow day limits if (iDays == -1) { // infinite history return; } it = items.iterator(); while (it.hasNext()) { HistoryItem hi = it.next(); if (hi.getDate() < (System.currentTimeMillis() - (((long) iDays) * Const.MILLISECONDS_IN_A_DAY))) { it.remove(); } } } /** * Write history on disk. * * @throws IOException Signals that an I/O exception has occurred. */ public static void commit() throws IOException { if (lDateStart == 0) { lDateStart = System.currentTimeMillis(); } java.io.File out = SessionService.getConfFileByPath(Const.FILE_HISTORY + "." + Const.FILE_SAVING_FILE_EXTENSION); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out), "UTF-8")); try { bw.write("<?xml version='1.0' encoding='UTF-8'?>\n"); bw.write("<history JAJUK_VERSION='" + Const.JAJUK_VERSION + "' begin_date='" + Long.toString(lDateStart) + "'>\n"); Iterator<HistoryItem> it = items.iterator(); while (it.hasNext()) { HistoryItem hi = it.next(); bw.write("\t<play file='" + hi.getFileId() + "' date='" + hi.getDate() + "'/>\n"); } bw.write("</history>"); bw.flush(); } finally { bw.close(); } java.io.File finalFile = SessionService.getConfFileByPath(Const.FILE_HISTORY); UtilSystem.saveFileWithRecoverySupport(finalFile); Log.debug("History commited to : " + finalFile.getAbsolutePath()); } /** * Read history from disk. * */ public static void load() { try { File historyFile = SessionService.getConfFileByPath(Const.FILE_HISTORY); UtilSystem.recoverFileIfRequired(historyFile); SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setValidating(false); SAXParser saxParser = spf.newSAXParser(); saxParser.parse(historyFile.toURI().toURL().toString(), getInstance()); // delete old history items getInstance().clear(Integer.parseInt(Conf.getString(Const.CONF_HISTORY))); } catch (Exception e) { Log.error(new JajukException(119)); try { commit(); // this history looks corrupted, write a void one } catch (Exception e2) { Log.error(e2); } } } /** * Return the history item by index. * * @param index * * @return the history item */ public HistoryItem getHistoryItem(int index) { return (index >= 0 && index < items.size() ? items.get(index) : null); } /** * parsing warning. * * @param spe * @throws SAXException the SAX exception */ @Override public void warning(SAXParseException spe) throws SAXException { throw new SAXException(Messages.getErrorMessage(119) + " / " + spe.getSystemId() + "/" + spe.getLineNumber() + "/" + spe.getColumnNumber() + " : " + spe.getMessage()); } /** * parsing error. * * @param spe * @throws SAXException the SAX exception */ @Override public void error(SAXParseException spe) throws SAXException { throw new SAXException(Messages.getErrorMessage(119) + " / " + spe.getSystemId() + "/" + spe.getLineNumber() + "/" + spe.getColumnNumber() + " : " + spe.getMessage()); } /** * parsing fatal error. * * @param spe * @throws SAXException the SAX exception */ @Override public void fatalError(SAXParseException spe) throws SAXException { throw new SAXException(Messages.getErrorMessage(119) + " / " + spe.getSystemId() + "/" + spe.getLineNumber() + "/" + spe.getColumnNumber() + " : " + spe.getMessage()); } /** * Called when we start an element. * * @param sUri * @param sName * @param sQName * @param attributes * * @throws SAXException the SAX exception */ @Override public void startElement(String sUri, String sName, String sQName, Attributes attributes) throws SAXException { if ("history".equals(sQName)) { setStartDate(UtilString .fastLongParser(attributes.getValue(attributes.getIndex("begin_date")))); } else if ("play".equals(sQName)) { String sID = attributes.getValue(attributes.getIndex("file")); // check id has not been changed Map<String, String> hm = Collection.getInstance().getHmWrongRightFileID(); if (hm.size() > 0 && hm.containsKey(sID)) { sID = hm.get(sID); Log.debug("upgrade ID:" + sID); } // test if this file is still known in the collection if (FileManager.getInstance().getFileByID(sID) != null) { HistoryItem hi = new HistoryItem(sID, UtilString.fastLongParser(attributes .getValue(attributes.getIndex("date")))); items.add(hi); } } } /* * (non-Javadoc) * * @see org.jajuk.ui.Observer#update(java.lang.String) */ @Override public void update(final JajukEvent event) { JajukEvents subject = event.getSubject(); try { if (JajukEvents.FILE_LAUNCHED.equals(subject)) { String sFileID = (String) ObservationManager.getDetail(event, Const.DETAIL_CURRENT_FILE_ID); long lDate = ((Long) ObservationManager.getDetail(event, Const.DETAIL_CURRENT_DATE)) .longValue(); addItem(sFileID, lDate); } else if (JajukEvents.DEVICE_REFRESH.equals(subject)) { cleanup(); } else if (JajukEvents.CLEAR_HISTORY.equals(subject)) { clear(); InformationJPanel.getInstance().setMessage(Messages.getString("ParameterView.251"), InformationJPanel.MessageType.INFORMATIVE); } else if (JajukEvents.LANGUAGE_CHANGED.equals(subject)) { // reset formatter formatter = new SimpleDateFormat(Messages.getString("HistoryItem.0"), Locale.getDefault()); } else if (JajukEvents.FILE_NAME_CHANGED.equals(subject)) { Properties properties = event.getDetails(); org.jajuk.base.File fileOld = (org.jajuk.base.File) properties.get(Const.DETAIL_OLD); org.jajuk.base.File fNew = (org.jajuk.base.File) properties.get(Const.DETAIL_NEW); // change id in history changeID(fileOld.getID(), fNew.getID()); } } catch (Exception e) { Log.error(e); return; } } /** * Gets the date formatter. * * @return Cached date formatter */ public SimpleDateFormat getDateFormatter() { return this.formatter; } /** * Sets the start date. * * @param start the new start date */ public static void setStartDate(long start) { lDateStart = start; } }