/* * 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.core; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; import org.jajuk.base.Collection; import org.jajuk.base.DeviceManager; import org.jajuk.services.bookmark.History; import org.jajuk.services.players.QueueModel; import org.jajuk.services.players.StackItem; import org.jajuk.services.webradio.CustomRadiosPersistenceHelper; import org.jajuk.services.webradio.PresetRadiosPersistenceHelper; import org.jajuk.ui.perspectives.IPerspective; import org.jajuk.util.log.Log; /** * This thread is responsible for commiting configuration or collection files on events. * This allows to save files during Jajuk running and not only when exiting the app as before. * <p> * It is sometimes difficult to get clear events to check to so we also start a differential check * on a regular basis through a thread * </p> * <p> * Singleton * <p> */ public final class PersistenceService extends Thread { public enum Urgency { HIGH, MEDIUM, LOW } private static PersistenceService self = new PersistenceService(); private String lastCommitQueueCheckSum; private static final int HEART_BEAT_MS = 1000; private static final int MIN_DELAY_AFTER_PERSPECTIVE_CHANGE_MS = 5000; private static final int DELAY_HIGH_URGENCY_BEATS = 5; private static final int DELAY_MEDIUM_URGENCY_BEATS = 15; private static final int DELAY_LOW_URGENCY_BEATS = 600 * HEART_BEAT_MS; /** Collection change flag **/ private volatile Map<Urgency, Boolean> collectionChanged = new HashMap<Urgency, Boolean>(3); private volatile boolean radiosChanged = false; private volatile boolean historyChanged = false; private volatile Map<IPerspective, Long> dateMinNextPerspectiveCommit = new HashMap<IPerspective, Long>( 10); /** * Inform the persister service that the perspective should be commited * @param perspective the perspective that changed */ public void setPerspectiveChanged(IPerspective perspective) { synchronized (dateMinNextPerspectiveCommit) { dateMinNextPerspectiveCommit.put(perspective, (System.currentTimeMillis() + MIN_DELAY_AFTER_PERSPECTIVE_CHANGE_MS)); } } /** * Inform the persister service that the history should be commited */ public void setHistoryChanged() { historyChanged = true; } /** * Inform the persister service that the collection should be commited with the given urgency * @param urgency the urgency for the collection to be commited */ public void setCollectionChanged(Urgency urgency) { collectionChanged.put(urgency, true); } /** * Inform the persister service that the radios should be commited */ public void setRadiosChanged() { radiosChanged = true; } /** * Instantiates a new rating manager. */ private PersistenceService() { // set thread name super("Persistence Manager Thread"); } /* * (non-Javadoc) * * @see java.lang.Thread#run() */ @Override public void run() { init(); int comp = 1; while (!ExitService.isExiting()) { try { Thread.sleep(HEART_BEAT_MS); if (comp % DELAY_HIGH_URGENCY_BEATS == 0) { performHighUrgencyActions(); } if (comp % DELAY_MEDIUM_URGENCY_BEATS == 0) { performMediumUrgencyActions(); } if (comp % DELAY_LOW_URGENCY_BEATS == 0) { performLowUrgencyActions(); } comp++; } catch (Exception e) { Log.error(e); } } } private void init() { this.lastCommitQueueCheckSum = getQueueModelChecksum(); collectionChanged.put(Urgency.LOW, false); collectionChanged.put(Urgency.MEDIUM, false); collectionChanged.put(Urgency.HIGH, false); setPriority(Thread.MAX_PRIORITY); } private void performHighUrgencyActions() throws Exception { commitWebradiosIfRequired(); if (collectionChanged.get(Urgency.HIGH) && !DeviceManager.getInstance().isAnyDeviceRefreshing()) { try { Collection.commit(); } finally { collectionChanged.put(Urgency.HIGH, false); } } } private void performMediumUrgencyActions() throws Exception { // Queue commitQueueModelIfRequired(); // Collection if (collectionChanged.get(Urgency.MEDIUM) && !DeviceManager.getInstance().isAnyDeviceRefreshing()) { try { Collection.commit(); } finally { collectionChanged.put(Urgency.MEDIUM, false); } } // Perspectives handcommitPerspectivesIfRequired(); } private void handcommitPerspectivesIfRequired() throws Exception { List<IPerspective> datesCopy = new ArrayList<IPerspective>( dateMinNextPerspectiveCommit.keySet()); for (IPerspective perspective : datesCopy) { if (System.currentTimeMillis() - dateMinNextPerspectiveCommit.get(perspective) >= 0) { try { perspective.commit(); } finally { synchronized (dateMinNextPerspectiveCommit) { dateMinNextPerspectiveCommit.remove(perspective); } } } } } private void performLowUrgencyActions() throws Exception { //History commitHistoryIfRequired(); // Collection if (collectionChanged.get(Urgency.LOW) && !DeviceManager.getInstance().isAnyDeviceRefreshing()) { try { Collection.commit(); } finally { collectionChanged.put(Urgency.LOW, false); } } } private void commitWebradiosIfRequired() throws IOException { try { if (radiosChanged) { // Commit webradios CustomRadiosPersistenceHelper.commit(); PresetRadiosPersistenceHelper.commit(); } } finally { radiosChanged = false; } } private void commitQueueModelIfRequired() throws IOException { String checksum = getQueueModelChecksum(); if (!checksum.equals(this.lastCommitQueueCheckSum)) { try { QueueModel.commit(); } finally { this.lastCommitQueueCheckSum = checksum; } } } private void commitHistoryIfRequired() throws IOException { if (historyChanged) { try { History.commit(); } finally { historyChanged = false; } } } private String getQueueModelChecksum() { StringBuilder sb = new StringBuilder(); for (StackItem item : QueueModel.getQueue()) { sb.append(item.getFile().getID()); } // Do not use MD5Processor class here to avoid the intern() method that // could create a permgen memory leak byte[] checksum = DigestUtils.md5(sb.toString()); return new String(checksum); } public static PersistenceService getInstance() { return self; } }