/* * 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.base; import java.io.File; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.swing.JOptionPane; import org.jajuk.events.JajukEvent; import org.jajuk.events.JajukEvents; import org.jajuk.events.ObservationManager; import org.jajuk.services.core.ExitService; import org.jajuk.services.players.QueueModel; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.Messages; import org.jajuk.util.ReadOnlyIterator; import org.jajuk.util.UpgradeManager; import org.jajuk.util.UtilSystem; import org.jajuk.util.log.Log; /** * Convenient class to manage devices. */ public final class DeviceManager extends ItemManager { /** Self instance. */ private static DeviceManager singleton = new DeviceManager(); /** Date last global refresh. */ private long lDateLastGlobalRefresh = 0; /** List of deep-refresh devices after an upgrade. */ private final Set<Device> devicesDeepRefreshed = new HashSet<Device>(); /** Auto-refresh thread. */ private final Thread tAutoRefresh = new Thread("Device Auto Refresh Thread") { @Override public void run() { while (!ExitService.isExiting()) { try { Thread.sleep(Const.AUTO_REFRESH_DELAY); refreshAllDevices(); } catch (Exception e) { Log.error(e); } } } }; private volatile boolean bGlobalRefreshing = false; /** * No constructor available, only static access. */ private DeviceManager() { super(); // register properties // ID registerProperty(new PropertyMetaInformation(Const.XML_ID, false, true, false, false, false, String.class, null)); // Name registerProperty(new PropertyMetaInformation(Const.XML_NAME, false, true, true, false, false, String.class, null)); // Type registerProperty(new PropertyMetaInformation(Const.XML_TYPE, false, true, true, false, false, Long.class, null)); // URL registerProperty(new PropertyMetaInformation(Const.XML_URL, false, true, true, false, false, Long.class, null)); // Auto-mount registerProperty(new PropertyMetaInformation(Const.XML_DEVICE_AUTO_MOUNT, false, true, true, false, false, Boolean.class, null)); // Auto-refresh registerProperty(new PropertyMetaInformation(Const.XML_DEVICE_AUTO_REFRESH, false, true, true, false, false, Double.class, 0d)); // Expand registerProperty(new PropertyMetaInformation(Const.XML_EXPANDED, false, false, false, false, true, Boolean.class, false)); // Synchro source registerProperty(new PropertyMetaInformation(Const.XML_DEVICE_SYNCHRO_SOURCE, false, false, true, false, false, String.class, null)); // Synchro mode registerProperty(new PropertyMetaInformation(Const.XML_DEVICE_SYNCHRO_MODE, false, false, true, false, false, String.class, null)); } /** * Start auto refresh thread. * */ public void startAutoRefreshThread() { if (!tAutoRefresh.isAlive()) { tAutoRefresh.setPriority(Thread.MIN_PRIORITY); tAutoRefresh.start(); } } /** * Gets the instance. * * @return singleton */ public static DeviceManager getInstance() { return singleton; } /** * Register a device. * * @param sName * @param deviceType * @param sUrl * * @return device */ public Device registerDevice(String sName, Device.Type deviceType, String sUrl) { String sId = createID(sName); return registerDevice(sId, sName, deviceType, sUrl); } /** * Register a device with a known id. * * @param sId * @param sName * @param deviceType * @param sUrl * @return device */ Device registerDevice(String sId, String sName, Device.Type deviceType, String sUrl) { Device device = getDeviceByID(sId); if (device != null) { return device; } device = new Device(sId, sName); device.setProperty(Const.XML_TYPE, (long) deviceType.ordinal()); device.setUrl(sUrl); registerItem(device); return device; } /** * Check none device already has this name or is a parent directory. * * @param sName * @param deviceType * @param sUrl * @param bNew * @return 0:ok or error code */ public int checkDeviceAvailablity(String sName, Device.Type deviceType, String sUrl, boolean bNew) { // don't check if it is a CD as all CDs may use the same mount point if (deviceType == Device.Type.FILES_CD) { return 0; } // check name and path for (Device deviceToCheck : DeviceManager.getInstance().getDevices()) { // If we check an existing device unchanged, just leave if (!bNew && sUrl.equals(deviceToCheck.getUrl())) { continue; } // check for a new device with an existing name if (bNew && (sName.equalsIgnoreCase(deviceToCheck.getName()))) { return 19; } String sUrlChecked = deviceToCheck.getUrl(); // check it is not a sub-directory of an existing device File fNew = new File(sUrl); File fChecked = new File(sUrlChecked); if (fNew.equals(fChecked) || UtilSystem.isDescendant(fNew, fChecked) || UtilSystem.isAncestor(fNew, fChecked)) { return 29; } } // check availability if (deviceType != Device.Type.EXTDD) { // not a remote device, TBI for remote // test directory is available File file = new File(sUrl); // check if the url exists and is readable if (!file.exists() || !file.canRead()) { return 143; } } return 0; } /** * Return first device found being parent of the provided path. * * @param path * * @return first device found being parent of the provided path */ Device getDeviceByPath(File path) { for (Device device : getDevices()) { if (UtilSystem.isAncestor(device.getFIO(), path)) { return device; } } return null; } /** * Remove a device. * * @param device */ public void removeDevice(Device device) { lock.writeLock().lock(); try { // show confirmation message if required if (Conf.getBoolean(Const.CONF_CONFIRMATIONS_REMOVE_DEVICE)) { int iResu = Messages.getChoice(Messages.getString("Confirmation_remove_device"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (iResu != JOptionPane.YES_OPTION) { return; } } // if device is refreshing or synchronizing, just leave if (device.isSynchronizing() || device.isRefreshing()) { Messages.showErrorMessage(13); return; } // check if device can be unmounted if (!QueueModel.canUnmount(device)) { Messages.showErrorMessage(121); return; } // if it is mounted, try to unmount it if (device.isMounted()) { try { device.unmount(); } catch (Exception e) { Messages.showErrorMessage(13); return; } } removeItem(device); DirectoryManager.getInstance().cleanDevice(device.getID()); FileManager.getInstance().cleanDevice(device.getID()); PlaylistManager.getInstance().cleanDevice(device.getID()); // Clean the collection up org.jajuk.base.Collection.cleanupLogical(); // remove synchronization if another device was synchronized // with this device for (Device deviceToCheck : getDevices()) { if (deviceToCheck.containsProperty(Const.XML_DEVICE_SYNCHRO_SOURCE)) { String sSyncSource = deviceToCheck.getStringValue(Const.XML_DEVICE_SYNCHRO_SOURCE); if (sSyncSource.equals(device.getID())) { deviceToCheck.removeProperty(Const.XML_DEVICE_SYNCHRO_SOURCE); } } } // Force suggestion view refresh to avoid showing removed albums ObservationManager.notify(new JajukEvent(JajukEvents.SUGGESTIONS_REFRESH)); } finally { lock.writeLock().unlock(); } } /** * Checks if is any device refreshing. * * @return whether any device is currently refreshing */ public boolean isAnyDeviceRefreshing() { boolean bOut = false; for (Device device : DeviceManager.getInstance().getDevices()) { if (device.isRefreshing()) { bOut = true; break; } } return bOut; } /* * (non-Javadoc) * * @see org.jajuk.base.ItemManager#getIdentifier() */ @Override public String getXMLTag() { return Const.XML_DEVICES; } /** * Gets the date last global refresh. * * @return the date last global refresh */ public long getDateLastGlobalRefresh() { return lDateLastGlobalRefresh; } /** * Refresh of all devices with auto-refresh enabled (used in automatic mode) * Must be the shortest possible. */ void refreshAllDevices() { try { // check thread is not already refreshing if (bGlobalRefreshing) { return; } bGlobalRefreshing = true; lDateLastGlobalRefresh = System.currentTimeMillis(); boolean bNeedUIRefresh = false; for (Device device : getDevices()) { // Do not auto-refresh CD as several CD may share the same mount // point if (device.getType() == Device.Type.FILES_CD) { continue; } double frequency = 60000 * device.getDoubleValue(Const.XML_DEVICE_AUTO_REFRESH); // check if this device needs auto-refresh if (frequency == 0d || device.getDateLastRefresh() > (System.currentTimeMillis() - frequency)) { continue; } /* * Check if devices contains files, otherwise it is not mounted we have to check this * because of the automatic cleaner thread musn't remove all references */ File[] files = new File(device.getUrl()).listFiles(); if (!device.isRefreshing() && files != null && files.length > 0) { /* * Check if this device should be deep-refresh after an upgrade */ boolean bNeedDeepAfterUpgrade = UpgradeManager.isMajorMigration() && !devicesDeepRefreshed.contains(device); if (bNeedDeepAfterUpgrade) { // Store this device to avoid duplicate deep refreshes devicesDeepRefreshed.add(device); } // cleanup device bNeedUIRefresh = bNeedUIRefresh | device.cleanRemovedFiles(null); // refresh the device (deep refresh forced after an upgrade) bNeedUIRefresh = bNeedUIRefresh | device.refreshCommand(bNeedDeepAfterUpgrade, false, null); // UI refresh if required if (bNeedUIRefresh) { // Cleanup logical items Collection.cleanupLogical(); /* * Notify views to refresh once the device is refreshed, do not wait all devices * refreshing as it may be tool long */ ObservationManager.notify(new JajukEvent(JajukEvents.DEVICE_REFRESH)); } } } // Display end of refresh message with stats } catch (Exception e) { Log.error(e); } finally { bGlobalRefreshing = false; } } /** * Gets the device by id. * * @param sID Item ID * * @return Element */ public Device getDeviceByID(String sID) { return (Device) getItemByID(sID); } /** * Gets the device by name. * * @param sName device name * * @return device by given name or null if no match */ public Device getDeviceByName(String sName) { for (Device device : getDevices()) { if (device.getName().equals(sName)) { return device; } } return null; } /** * Gets the devices. * * @return ordered devices list */ @SuppressWarnings("unchecked") public List<Device> getDevices() { return (List<Device>) getItems(); } /** * Gets the devices iterator. * * @return devices iterator */ @SuppressWarnings("unchecked") public ReadOnlyIterator<Device> getDevicesIterator() { return new ReadOnlyIterator<Device>((Iterator<Device>) getItemsIterator()); } }