org.jajuk.ui.views.CoverView.java Source code

Java tutorial

Introduction

Here is the source code for org.jajuk.ui.views.CoverView.java

Source

/*
 *  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.ui.views;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

import net.miginfocom.swing.MigLayout;

import org.apache.commons.lang.StringUtils;
import org.jajuk.base.Album;
import org.jajuk.base.Artist;
import org.jajuk.base.Directory;
import org.jajuk.base.Track;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.services.covers.Cover;
import org.jajuk.services.covers.Cover.CoverType;
import org.jajuk.services.players.QueueModel;
import org.jajuk.services.players.StackItem;
import org.jajuk.services.tags.Tag;
import org.jajuk.ui.helpers.JajukMouseAdapter;
import org.jajuk.ui.thumbnails.ThumbnailManager;
import org.jajuk.ui.widgets.InformationJPanel;
import org.jajuk.ui.widgets.JajukButton;
import org.jajuk.ui.widgets.JajukFileChooser;
import org.jajuk.ui.widgets.JajukJToolbar;
import org.jajuk.ui.windows.JajukMainWindow;
import org.jajuk.util.Conf;
import org.jajuk.util.Const;
import org.jajuk.util.DownloadManager;
import org.jajuk.util.IconLoader;
import org.jajuk.util.JajukFileFilter;
import org.jajuk.util.JajukIcons;
import org.jajuk.util.Messages;
import org.jajuk.util.UtilFeatures;
import org.jajuk.util.UtilGUI;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.error.JajukException;
import org.jajuk.util.filters.GIFFilter;
import org.jajuk.util.filters.ImageFilter;
import org.jajuk.util.filters.JPGFilter;
import org.jajuk.util.filters.PNGFilter;
import org.jajuk.util.log.Log;

/**
 * Cover view. Displays an image for the current album
 */
public class CoverView extends ViewAdapter implements ActionListener {
    /** The Constant PLUS_QUOTE.   */
    private static final String PLUS_QUOTE = "+\"";
    /** The Constant QUOTE_BLANK.   */
    private static final String QUOTE_BLANK = "\" ";
    /** Generated serialVersionUID. */
    private static final long serialVersionUID = 1L;
    /** No cover cover. */
    private static Cover nocover = new Cover(Const.IMAGES_SPLASHSCREEN, CoverType.NO_COVER);
    /** Error counter to check connection availability. */
    private volatile static int iErrorCounter = 0;
    /** Connected one flag : true if jajuk managed once to connect to the web to bring covers. */
    private volatile static boolean bOnceConnected = false;
    /** Reference File for cover. */
    private volatile org.jajuk.base.File fileReference;
    /** File directory used as a cache for perfs. */
    private volatile Directory dirReference;
    /** List of available covers for the current file. */
    private final LinkedList<Cover> alCovers = new LinkedList<Cover>();
    // control panel
    private JPanel jpControl;
    private JajukButton jbPrevious;
    private JajukButton jbNext;
    private JajukButton jbDelete;
    private JajukButton jbSave;
    private JajukButton jbDefault;
    private JLabel jlSize;
    private JLabel jlFound;
    /** Cover search accuracy combo. */
    @SuppressWarnings("rawtypes")
    private JComboBox jcbAccuracy;
    /** Date last resize (used for adjustment management). */
    private volatile long lDateLastResize;
    /** URL and size of the image. */
    private JLabel jl;
    /** Used Cover index. */
    private volatile int index = 0;
    /** Flag telling that user wants to display a better cover. */
    private boolean bGotoBetter = false;
    /** Final image to display. */
    private volatile ImageIcon ii;
    /** Force next track cover reload flag*. */
    private volatile boolean bForceCoverReload = true;
    private boolean includeControls;
    /** Whether the view has not yet been displayed for its first time */
    private volatile boolean initEvent = true;
    /** Used to lock access to covers collection, we don't synchronize this as this may create thread 
     * starvations because the EDT requires this lock as well in ViewAdater.stopAllBusyLabels() method. */
    private final Lock listLock = new ReentrantLock(true);
    /** Parent of this view. */
    private JDialog parentJDialog;

    /**
     * Constructor.
     */
    public CoverView() {
        super();
    }

    /**
     * Constructor.
     * 
     * @param file Reference file. Used to display cover for a particular file, null if the cover view is used in the "reular" way as a view, not 
     * as a dialog from catalog view for ie.
     */
    public CoverView(final org.jajuk.base.File file) {
        super();
        fileReference = file;
    }

    /**
     * Constructor.
     * 
     * @param file Reference file. Used to display cover for a particular file, null if the cover view is used in the "reular" way as a view, not 
     * as a dialog from catalog view for ie.
     * @param jd parent dialog (closed after a default cover is selected) 
     */
    public CoverView(final org.jajuk.base.File file, JDialog jd) {
        super();
        fileReference = file;
        parentJDialog = jd;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.ui.views.IView#initUI()
     */
    @Override
    public void initUI() {
        initUI(true);
    }

    /**
     * Inits the ui.
     *  
     * @param includeControls 
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void initUI(boolean includeControls) {
        this.includeControls = includeControls;
        // Control panel
        jpControl = new JPanel();
        if (includeControls) {
            jpControl.setBorder(BorderFactory.createEtchedBorder());
        }
        final JToolBar jtb = new JajukJToolbar();
        jbPrevious = new JajukButton(IconLoader.getIcon(JajukIcons.PLAYER_PREVIOUS_SMALL));
        jbPrevious.addActionListener(this);
        jbPrevious.setToolTipText(Messages.getString("CoverView.4"));
        jbNext = new JajukButton(IconLoader.getIcon(JajukIcons.PLAYER_NEXT_SMALL));
        jbNext.addActionListener(this);
        jbNext.setToolTipText(Messages.getString("CoverView.5"));
        jbDelete = new JajukButton(IconLoader.getIcon(JajukIcons.DELETE));
        jbDelete.addActionListener(this);
        jbDelete.setToolTipText(Messages.getString("CoverView.2"));
        jbSave = new JajukButton(IconLoader.getIcon(JajukIcons.SAVE));
        jbSave.addActionListener(this);
        jbSave.setToolTipText(Messages.getString("CoverView.6"));
        jbDefault = new JajukButton(IconLoader.getIcon(JajukIcons.DEFAULT_COVER));
        jbDefault.addActionListener(this);
        jbDefault.setToolTipText(Messages.getString("CoverView.8"));
        jlSize = new JLabel("");
        jlFound = new JLabel("");
        jcbAccuracy = new JComboBox();
        // Add tooltips on combo items
        jcbAccuracy.setRenderer(new BasicComboBoxRenderer() {
            private static final long serialVersionUID = -6943363556191659895L;

            @Override
            public Component getListCellRendererComponent(final JList list, final Object value, final int index,
                    final boolean isSelected, final boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                switch (index) {
                case 0:
                    setToolTipText(Messages.getString("ParameterView.156"));
                    break;
                case 1:
                    setToolTipText(Messages.getString("ParameterView.157"));
                    break;
                case 2:
                    setToolTipText(Messages.getString("ParameterView.158"));
                    break;
                case 3:
                    setToolTipText(Messages.getString("ParameterView.216"));
                    break;
                case 4:
                    setToolTipText(Messages.getString("ParameterView.217"));
                    break;
                case 5:
                    setToolTipText(Messages.getString("ParameterView.218"));
                    break;
                }
                setBorder(new EmptyBorder(0, 3, 0, 3));
                return this;
            }
        });
        jcbAccuracy.setToolTipText(Messages.getString("ParameterView.155"));
        jcbAccuracy.addItem(IconLoader.getIcon(JajukIcons.ACCURACY_LOW));
        jcbAccuracy.addItem(IconLoader.getIcon(JajukIcons.ACCURACY_MEDIUM));
        jcbAccuracy.addItem(IconLoader.getIcon(JajukIcons.ACCURACY_HIGH));
        jcbAccuracy.addItem(IconLoader.getIcon(JajukIcons.ARTIST));
        jcbAccuracy.addItem(IconLoader.getIcon(JajukIcons.ALBUM));
        jcbAccuracy.addItem(IconLoader.getIcon(JajukIcons.TRACK));
        int accuracy = getCurrentAccuracy();
        jcbAccuracy.setSelectedIndex(accuracy);
        jcbAccuracy.addActionListener(this);
        jtb.add(jbPrevious);
        jtb.add(jbNext);
        jtb.addSeparator();
        jtb.add(jbDelete);
        jtb.add(jbSave);
        jtb.add(jbDefault);
        if (includeControls) {
            jpControl.setLayout(new MigLayout("insets 5 2 5 2", "[][grow][grow][]"));
            jpControl.add(jtb);
            jpControl.add(jlSize, "center,gapright 5::");
            jpControl.add(jlFound, "center,gapright 5::");
            jpControl.add(jcbAccuracy, "grow,width 47!,gapright 5");
        }
        // Cover view used in catalog view should not listen events
        if (fileReference == null) {
            ObservationManager.register(this);
        }
        // global layout
        MigLayout globalLayout = null;
        if (includeControls) {
            globalLayout = new MigLayout("ins 0,gapy 10", "[grow]", "[30!][grow]");
        } else {
            globalLayout = new MigLayout("ins 0,gapy 10", "[grow]", "[grow]");
        }
        setLayout(globalLayout);
        add(jpControl, "grow,wrap");
        //Force initial resizing (required after a perspective reset as the component event is not thrown in this case)
        componentResized(null);
        // Attach the listener for initial cover display and further manual actions against the view when resizing.
        addComponentListener(CoverView.this);
    }

    /*
     * (non-Javadoc)
     *
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    @Override
    public void actionPerformed(final ActionEvent e) {
        if (e.getSource() == jcbAccuracy) {
            handleAccuracy();
        } else if (e.getSource() == jbPrevious) { // previous : show a
            handlePrevious();
        } else if (e.getSource() == jbNext) { // next : show a worse cover
            handleNext();
        } else if (e.getSource() == jbDelete) { // delete a local cover
            handleDelete();
        } else if (e.getSource() == jbDefault) {
            handleDefault();
        } else if ((e.getSource() == jbSave)
                && ((e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK)) {
            // save a file as... (can be local now)
            handleSaveAs();
        } else if (e.getSource() == jbSave) {
            handleSave();
        }
    }

    /**
     * Stores accuracy.
     */
    private void handleAccuracy() {
        // Note that we have to store/retrieve accuracy using an id. When
        // this view is used from a popup, we can't use perspective id
        Conf.setProperty(
                Const.CONF_COVERS_ACCURACY + "_"
                        + ((getPerspective() == null) ? "popup" : getPerspective().getID()),
                Integer.toString(jcbAccuracy.getSelectedIndex()));
        new Thread("Cover Accuracy Thread") {
            @Override
            public void run() {
                // force refresh
                if (getPerspective() == null) {
                    dirReference = null;
                }
                update(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
            }
        }.start();
    }

    /**
     * Called on the previous cover button event.
     */
    private void handlePrevious() {
        // better cover
        bGotoBetter = true;
        index++;
        if (index > alCovers.size() - 1) {
            index = 0;
        }
        displayCurrentCover();
        bGotoBetter = false; // make sure default behavior is to go
        // to worse covers
    }

    /**
     * Called on the next cover button event.
     */
    private void handleNext() {
        bGotoBetter = false;
        index--;
        if (index < 0) {
            index = alCovers.size() - 1;
        }
        displayCurrentCover();
    }

    /**
     * Called on the delete cover button event.
     */
    private void handleDelete() {
        // sanity check
        if (index >= alCovers.size()) {
            Log.warn("Cannot delete cover that is not available.");
            return;
        }
        if (index < 0) {
            Log.warn("Cannot delete cover with invalid index.");
            return;
        }
        // get the cover at the specified position
        final Cover cover = alCovers.get(index);
        // don't delete the splashscreen-jpg!!
        if (cover.getType().equals(CoverType.NO_COVER)) {
            Log.warn("Cannot delete default Jajuk cover.");
            return;
        }
        // show confirmation message if required
        if (Conf.getBoolean(Const.CONF_CONFIRMATIONS_DELETE_COVER)) {
            final int iResu = Messages.getChoice(
                    Messages.getString("Confirmation_delete_cover") + " : " + cover.getFile(),
                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
            if (iResu != JOptionPane.YES_OPTION) {
                return;
            }
        }
        // yet there? ok, delete the cover
        try {
            final File file = cover.getFile();
            if (file.isFile() && file.exists()) {
                UtilSystem.deleteFile(file);
            } else { // not a file, must have a problem
                throw new Exception("Encountered file which either is not a file or does not exist: " + file);
            }
        } catch (final Exception ioe) {
            Log.error(131, ioe);
            Messages.showErrorMessage(131);
            return;
        }
        // If this was the absolute cover, remove the reference in the
        // collection
        if (cover.getType() == CoverType.SELECTED_COVER) {
            dirReference.removeProperty("default_cover");
        }
        // reorganize covers
        try {
            listLock.lock();
            alCovers.remove(index);
            index--;
            if (index < 0) {
                index = alCovers.size() - 1;
            }
            ObservationManager.notify(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
            if (fileReference != null) {
                update(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
            }
        } finally {
            listLock.unlock();
        }
    }

    /**
     * Called when saving a cover.
     */
    private void handleSave() {
        // sanity check
        if (index >= alCovers.size()) {
            Log.warn("Cannot save cover that is not available.");
            return;
        }
        if (index < 0) {
            Log.warn("Cannot save cover with invalid index.");
            return;
        }
        // save a file with its original name
        new Thread("Cover Save Thread") {
            @Override
            public void run() {
                final Cover cover = alCovers.get(index);
                // should not happen, only remote covers here
                if (cover.getType() != CoverType.REMOTE_COVER) {
                    Log.debug("Try to save a local cover");
                    return;
                }
                String sFilePath = null;
                sFilePath = dirReference.getFio().getPath() + "/"
                        + UtilSystem.getOnlyFile(cover.getURL().toString());
                sFilePath = convertCoverPath(sFilePath);
                try {
                    // copy file from cache
                    final File fSource = DownloadManager.downloadToCache(cover.getURL());
                    final File file = new File(sFilePath);
                    UtilSystem.copy(fSource, file);
                    InformationJPanel.getInstance().setMessage(Messages.getString("CoverView.11"),
                            InformationJPanel.MessageType.INFORMATIVE);
                    final Cover cover2 = new Cover(file, CoverType.SELECTED_COVER);
                    if (!alCovers.contains(cover2)) {
                        alCovers.add(cover2);
                        setFoundText();
                    }
                    // Reset cached cover in associated albums to make sure that new covers
                    // will be discovered in various views like Catalog View.
                    resetCachedCover();
                    // Notify cover change
                    ObservationManager.notify(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
                    // add new cover in others cover views
                } catch (final Exception ex) {
                    Log.error(24, ex);
                    Messages.showErrorMessage(24);
                }
            }
        }.start();
    }

    /**
     * Reset cached cover in associated albums to make sure that new covers
     *  will be discovered in various views like Catalog View.
     */
    private void resetCachedCover() {
        org.jajuk.base.File fCurrent = fileReference;
        if (fCurrent == null) {
            fCurrent = QueueModel.getPlayingFile();
        }
        Set<Album> albums = fCurrent.getDirectory().getAlbums();
        // If we cached NO_COVER for this album, make sure to reset this value
        for (Album album : albums) {
            String cachedCoverPath = album.getStringValue(XML_ALBUM_DISCOVERED_COVER);
            if (COVER_NONE.equals(cachedCoverPath)) {
                album.setProperty(XML_ALBUM_DISCOVERED_COVER, "");
            }
            ObservationManager.notify(new JajukEvent(JajukEvents.COVER_DEFAULT_CHANGED));
        }
    }

    /**
     * Converts a cover path according to options and jajuk conventions.
     *
     * @param sFilePath current cover path
     * @return the converted cover file path
     */
    private String convertCoverPath(String sFilePath) {
        int pos = sFilePath.lastIndexOf('.');
        if (Conf.getBoolean(Const.CONF_COVERS_SAVE_EXPLORER_FRIENDLY)) {
            // Covers should be stored as folder.xxx for windows explorer
            final String ext;
            if (pos == -1) {
                ext = "";
            } else {
                ext = sFilePath.substring(pos, sFilePath.length());
            }
            String parent = new File(sFilePath).getParent();
            return parent + System.getProperty("file.separator") + "Folder" + ext;
        } else {
            if (pos == -1) {
                return sFilePath + Const.FILE_JAJUK_DOWNLOADED_FILES_SUFFIX;
            }
            // Add a jajuk suffix to know this cover has been downloaded by jajuk
            return new StringBuilder(sFilePath).insert(pos, Const.FILE_JAJUK_DOWNLOADED_FILES_SUFFIX).toString();
        }
    }

    /**
     * Called when saving as a cover.
     */
    private void handleSaveAs() {
        // sanity check
        if (index >= alCovers.size()) {
            Log.warn("Cannot save cover that is not available.");
            return;
        }
        if (index < 0) {
            Log.warn("Cannot save cover with invalid index.");
            return;
        }
        new Thread("Cover SaveAs Thread") {
            @Override
            public void run() {
                final Cover cover = alCovers.get(index);
                final JajukFileChooser jfchooser = new JajukFileChooser(new JajukFileFilter(GIFFilter.getInstance(),
                        PNGFilter.getInstance(), JPGFilter.getInstance()));
                jfchooser.setAcceptDirectories(true);
                jfchooser.setCurrentDirectory(dirReference.getFio());
                jfchooser.setDialogTitle(Messages.getString("CoverView.10"));
                final File finalFile = new File(
                        dirReference.getFio().getPath() + "/" + UtilSystem.getOnlyFile(cover.getURL().toString()));
                jfchooser.setSelectedFile(finalFile);
                final int returnVal = jfchooser.showSaveDialog(JajukMainWindow.getInstance());
                File fNew = null;
                if (returnVal == JFileChooser.APPROVE_OPTION) {
                    fNew = jfchooser.getSelectedFile();
                    // if user try to save as without changing file name
                    if (fNew.getAbsolutePath().equals(cover.getFile().getAbsolutePath())) {
                        return;
                    }
                    try {
                        UtilSystem.copy(cover.getFile(), fNew);
                        InformationJPanel.getInstance().setMessage(Messages.getString("CoverView.11"),
                                InformationJPanel.MessageType.INFORMATIVE);
                        // Reset cached cover in associated albums to make sure that new covers
                        // will be discovered in various views like Catalog View.
                        resetCachedCover();
                        // Notify cover change
                        ObservationManager.notify(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
                    } catch (final Exception ex) {
                        Log.error(24, ex);
                        Messages.showErrorMessage(24);
                    }
                }
            }
        }.start();
    }

    /**
     * Called when making a cover default.
     */
    private void handleDefault() {
        // sanity check
        if (index >= alCovers.size()) {
            Log.warn("Cannot default cover which is not available.");
            return;
        }
        if (index < 0) {
            Log.warn("Cannot default cover with invalid index.");
            return;
        }
        new Thread("Default cover thread") {
            @Override
            public void run() {
                Cover cover = alCovers.get(index);
                org.jajuk.base.File fCurrent = fileReference;
                // Path of the default cover, it is simply the URL of the current cover for local covers
                // but it is another path to a newly created image for tag or remote covers
                String destPath = cover.getFile().getAbsolutePath();
                if (fCurrent == null) {
                    fCurrent = QueueModel.getPlayingFile();
                }
                if (cover.getType() == CoverType.TAG_COVER) {
                    destPath = dirReference.getFio().getPath() + "/" + cover.getFile().getName();
                    destPath = convertCoverPath(destPath);
                    File destFile = new File(destPath);
                    try {
                        // Copy cached file to music directory
                        // Note that the refreshCover() methods automatically 
                        // extract any track cover tag to an image file in the cache
                        UtilSystem.copy(cover.getFile(), destFile);
                        Cover cover2 = new Cover(destFile, CoverType.SELECTED_COVER);
                        alCovers.add(cover2);
                    } catch (Exception ex) {
                        Log.error(24, ex);
                        Messages.showErrorMessage(24);
                        return;
                    }
                } else if (cover.getType() == CoverType.REMOTE_COVER) {
                    String sFilename = UtilSystem.getOnlyFile(cover.getURL().toString());
                    destPath = dirReference.getFio().getPath() + "/" + sFilename;
                    destPath = convertCoverPath(destPath);
                    try {
                        // Download cover and copy file from cache to music directory
                        File fSource = DownloadManager.downloadToCache(cover.getURL());
                        File fileDest = new File(destPath);
                        UtilSystem.copy(fSource, new File(destPath));
                        Cover cover2 = new Cover(fileDest, CoverType.SELECTED_COVER);
                        if (!alCovers.contains(cover2)) {
                            alCovers.add(cover2);
                            setFoundText();
                        }
                    } catch (Exception ex) {
                        Log.error(24, ex);
                        Messages.showErrorMessage(24);
                        return;
                    }
                }
                // Remove previous thumbs to avoid using outdated images
                // Reset cached cover
                ThumbnailManager.cleanThumbs(fCurrent.getTrack().getAlbum());
                refreshThumbs(cover);
                InformationJPanel.getInstance().setMessage(Messages.getString("Success"),
                        InformationJPanel.MessageType.INFORMATIVE);
                // For every kind of cover types :
                ObservationManager.notify(new JajukEvent(JajukEvents.COVER_DEFAULT_CHANGED));
                ObservationManager.notify(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
                // then make it the default cover for this album
                if (fCurrent != null && fCurrent.getTrack() != null && fCurrent.getTrack().getAlbum() != null
                        && cover.getFile() != null) {
                    Album album = fCurrent.getTrack().getAlbum();
                    album.setProperty(XML_ALBUM_SELECTED_COVER, destPath);
                    album.setProperty(XML_ALBUM_DISCOVERED_COVER, destPath);
                }
                // Close the current dialog
                if (parentJDialog != null) {
                    parentJDialog.dispose();
                }
            }
        }.start();
    }

    /*
     * (non-Javadoc)
     *
     * @see java.awt.event.ComponentListener#componentResized(java.awt.event.ComponentEvent )
     */
    @Override
    public void componentResized(final ComponentEvent e) {
        Dimension dim = getSize();
        if (dim.getHeight() <= 0 || dim.getWidth() <= 0) {
            return;
        }
        final long lCurrentDate = System.currentTimeMillis(); // adjusting code
        if (lCurrentDate - lDateLastResize < 500) { // Do consider only one event every 
            // 500 ms to avoid race conditions and lead to unexpected states (verified)
            return;
        }
        lDateLastResize = lCurrentDate;
        Log.debug("Cover resized, view=" + getID() + " size=" + getSize());
        // Run this in another thread to accelerate the component resize events processing and filter by time
        new Thread() {
            @Override
            public void run() {
                if (fileReference == null) { // regular cover view
                    if (QueueModel.isStopped()) {
                        update(new JajukEvent(JajukEvents.ZERO));
                    }
                    // check if a track has already been launched
                    else if (QueueModel.isPlayingRadio()) {
                        update(new JajukEvent(JajukEvents.WEBRADIO_LAUNCHED,
                                ObservationManager.getDetailsLastOccurence(JajukEvents.WEBRADIO_LAUNCHED)));
                        // If the view is displayed for the first time, a ComponentResized event is launched at its first display but
                        // we want to perform the full process : update past launches files (FILE_LAUNCHED). 
                        // But if it is no more the initial resize event, we only want to refresh the cover, not the full story.
                    } else if (!initEvent) {
                        displayCurrentCover();
                    } else {
                        update(new JajukEvent(JajukEvents.FILE_LAUNCHED));
                    }
                } else { // cover view used as dialog
                    update(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
                }
                // It will never more be the first time ...
                CoverView.this.initEvent = false;
            }
        }.start();
    }

    /**
     * Creates the query.
     * 
     * @param file 
     * 
     * @return an accurate google search query for a file
     */
    public String createQuery(final org.jajuk.base.File file) {
        String query = "";
        int iAccuracy = getCurrentAccuracy();
        final Track track = file.getTrack();
        final Artist artist = track.getArtist();
        final Album album = track.getAlbum();
        switch (iAccuracy) {
        case 0: // low, default
            if (!artist.seemsUnknown()) {
                query += artist.getName() + " ";
            } else if (!track.getAlbumArtist().seemsUnknown()) {
                query += track.getAlbumArtist().getName() + " ";
            }
            if (!album.seemsUnknown()) {
                query += album.getName() + " ";
            }
            break;
        case 1: // medium
            if (!artist.seemsUnknown()) {
                query += '\"' + artist.getName() + QUOTE_BLANK;
            } else if (!track.getAlbumArtist().seemsUnknown()) {
                query += '\"' + track.getAlbumArtist().getName() + QUOTE_BLANK;
            }
            if (!album.seemsUnknown()) {
                query += '\"' + album.getName() + QUOTE_BLANK;
            }
            break;
        case 2: // high
            if (!artist.seemsUnknown()) {
                query += PLUS_QUOTE + artist.getName() + QUOTE_BLANK;
            } else if (!track.getAlbumArtist().seemsUnknown()) {
                query += PLUS_QUOTE + track.getAlbumArtist().getName() + QUOTE_BLANK;
            }
            if (!album.seemsUnknown()) {
                query += PLUS_QUOTE + album.getName() + QUOTE_BLANK;
            }
            break;
        case 3: // by artist
            if (!artist.seemsUnknown()) {
                query += artist.getName() + " ";
            } else if (!track.getAlbumArtist().seemsUnknown()) {
                query += track.getAlbumArtist().getName() + " ";
            }
            break;
        case 4: // by album
            if (!album.seemsUnknown()) {
                query += album.getName() + " ";
            }
            break;
        case 5: // by track name
            query += track.getName();
            break;
        default:
            break;
        }
        return query;
    }

    private int getCurrentAccuracy() {
        // Default = medium
        int iAccuracy = 1;
        try {
            iAccuracy = Conf.getInt(Const.CONF_COVERS_ACCURACY + "_"
                    + ((getPerspective() == null) ? "popup" : getPerspective().getID()));
        } catch (final NumberFormatException e) {
            // can append if accuracy never set
            Log.debug("Unknown accuracy");
        }
        return iAccuracy;
    }

    /**
     * Display current cover (at this.index), try all covers in case of error
     */
    private void displayCurrentCover() {
        UtilGUI.showBusyLabel(CoverView.this); // lookup icon
        findRightCover();
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                displayCover(index);
            }
        });
    }

    private void findRightCover() {
        try {
            listLock.lock();
            // Avoid looping
            if (alCovers.size() == 0) {
                // should not append
                alCovers.add(CoverView.nocover);
                // Add at last the default cover if all remote cover has
                // been discarded
                try {
                    prepareDisplay(0);
                } catch (JajukException e) {
                    Log.error(e);
                }
                return;
            }
            if (alCovers.size() == 1 && (alCovers.get(0)).getType() == CoverType.NO_COVER) {
                // only a default cover
                try {
                    prepareDisplay(0);
                } catch (JajukException e) {
                    Log.error(e);
                }
                return;
            }
            // else, there is at least one local cover and no
            // default cover
            while (alCovers.size() > 0) {
                try {
                    prepareDisplay(index);
                    return; // OK, leave
                } catch (Exception e) {
                    Log.debug("Removed cover: {{" + alCovers.get(index) + "}}");
                    alCovers.remove(index);
                    // refresh number of found covers
                    if (!bGotoBetter) {
                        // we go to worse covers. If we go to better
                        // covers, we just
                        // keep the same index try a worse cover...
                        if (index - 1 >= 0) {
                            index--;
                        } else { // no more worse cover
                            index = alCovers.size() - 1;
                            // come back to best cover
                        }
                    }
                }
            }
            // if this code is executed, it means than no available
            // cover was found, then display default cover
            alCovers.add(CoverView.nocover); // Add at last the default cover
            // if all remote cover has been discarded
            try {
                index = 0;
                prepareDisplay(index);
            } catch (JajukException e) {
                Log.error(e);
            }
        } finally {
            listLock.unlock();
        }
    }

    /**
     * Gets the cover number.
     * 
     * @return number of real covers (not default) covers found
     */
    private int getCoverNumber() {
        return alCovers.size();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.ui.IView#getDesc()
     */
    @Override
    public String getDesc() {
        return Messages.getString("CoverView.3");
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.events.Observer#getRegistrationKeys()
     */
    @Override
    public Set<JajukEvents> getRegistrationKeys() {
        final Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>();
        eventSubjectSet.add(JajukEvents.FILE_LAUNCHED);
        eventSubjectSet.add(JajukEvents.WEBRADIO_LAUNCHED);
        eventSubjectSet.add(JajukEvents.ZERO);
        eventSubjectSet.add(JajukEvents.PLAYER_STOP);
        eventSubjectSet.add(JajukEvents.COVER_NEED_REFRESH);
        return eventSubjectSet;
    }

    /**
     * Long action to compute image to display (download, resizing...)
     * 
     * @param index 
     * 
     * @throws JajukException the jajuk exception
     */
    private void prepareDisplay(final int index) throws JajukException {
        // find next correct cover
        Cover cover = null;
        ImageIcon icon = null;
        try {
            cover = alCovers.get(index); // take image at the given index
            Log.debug("Display cover: " + cover + " at index :" + index);
            Image img = cover.getImage();
            // Never mirror our no cover image
            if (cover.getType().equals(CoverType.NO_COVER)) {
                icon = new ImageIcon(img);
            } else {
                if (
                // should we mirror in our GUI
                (includeControls && Conf.getBoolean(Const.CONF_COVERS_MIRROW_COVER))
                        // should we mirror in fullscreen mode
                        || (!includeControls && Conf.getBoolean(Const.CONF_COVERS_MIRROW_COVER_FS_MODE))) {
                    icon = new ImageIcon(UtilGUI.get3dImage(img));
                } else {
                    icon = new ImageIcon(img);
                }
            }
            if (icon.getIconHeight() == 0 || icon.getIconWidth() == 0) {
                throw new JajukException(0, "Wrong picture, size is null");
            }
        } catch (final FileNotFoundException e) {
            // do not display a stacktrace for FileNotfound as we expect this in cases
            // where the picture is gone on the net
            Log.warn("Cover image not found at URL: " + (cover == null ? "<null>" : cover.getURL().toString()));
            throw new JajukException(0, e);
        } catch (final UnknownHostException e) {
            // do not display a stacktrace for HostNotFound as we expect this in cases
            // where the whole server is gone on the net
            Log.warn("Cover image not found at URL: " + (cover == null ? "<null>" : cover.getURL().toString()));
            throw new JajukException(0, e);
        } catch (final IOException e) { // this cover cannot be loaded
            Log.error(e);
            throw new JajukException(0, e);
        } catch (final InterruptedException e) { // this cover cannot be loaded
            Log.error(e);
            throw new JajukException(0, e);
        }
        // We apply a 90% of space availability to avoid image cut-offs (see #1283)
        final int iDisplayAreaHeight = (int) (0.9f * CoverView.this.getHeight() - 30);
        final int iDisplayAreaWidth = (int) (0.9f * CoverView.this.getWidth() - 10);
        // check minimum sizes
        if ((iDisplayAreaHeight < 1) || (iDisplayAreaWidth < 1)) {
            return;
        }
        int iNewWidth;
        int iNewHeight;
        if (iDisplayAreaHeight > iDisplayAreaWidth) {
            // Width is smaller than height : try to optimize height
            iNewHeight = iDisplayAreaHeight; // take all possible height
            // we check now if width will be visible entirely with optimized
            // height
            final float fHeightRatio = (float) iNewHeight / icon.getIconHeight();
            if (icon.getIconWidth() * fHeightRatio <= iDisplayAreaWidth) {
                iNewWidth = (int) (icon.getIconWidth() * fHeightRatio);
            } else {
                // no? so we optimize width
                iNewWidth = iDisplayAreaWidth;
                iNewHeight = (int) (icon.getIconHeight() * ((float) iNewWidth / icon.getIconWidth()));
            }
        } else {
            // Height is smaller or equal than width : try to optimize width
            iNewWidth = iDisplayAreaWidth; // take all possible width
            // we check now if height will be visible entirely with
            // optimized width
            final float fWidthRatio = (float) iNewWidth / icon.getIconWidth();
            if (icon.getIconHeight() * fWidthRatio <= iDisplayAreaHeight) {
                iNewHeight = (int) (icon.getIconHeight() * fWidthRatio);
            } else {
                // no? so we optimize width
                iNewHeight = iDisplayAreaHeight;
                iNewWidth = (int) (icon.getIconWidth() * ((float) iNewHeight / icon.getIconHeight()));
            }
        }
        // Note that at this point, the image is fully loaded (done in the ImageIcon constructor)
        ii = UtilGUI.getResizedImage(icon, iNewWidth, iNewHeight);
        // Free memory of source image, removing this causes severe memory leaks ! (tested)
        icon.getImage().flush();
    }

    /**
    * Display given cover.
    * 
    * @param index index of the cover to display
    */
    private void displayCover(final int index) {
        if ((alCovers.size() == 0) || (index >= alCovers.size()) || (index < 0)) {
            // just a check
            alCovers.add(CoverView.nocover); // display nocover by default
            displayCover(0);
            return;
        }
        final Cover cover = alCovers.get(index); // take image at the given index
        final URL url = cover.getURL();
        // enable delete button only for local covers
        jbDelete.setEnabled(cover.getType() == CoverType.LOCAL_COVER || cover.getType() == CoverType.SELECTED_COVER
                || cover.getType() == CoverType.STANDARD_COVER);
        //Disable default command for "none" cover
        jbDefault.setEnabled(cover.getType() != CoverType.NO_COVER);
        if (url != null) {
            jbSave.setEnabled(false);
            String sType = " (L)"; // local cover
            if (cover.getType() == CoverType.REMOTE_COVER) {
                sType = "(@)"; // Web cover
                jbSave.setEnabled(true);
            } else if (cover.getType() == CoverType.TAG_COVER) {
                sType = "(T)"; // Tag cover
            }
            final String size = cover.getSize();
            jl = new JLabel(ii);
            jl.setMinimumSize(new Dimension(0, 0)); // required for info
            // node resizing
            if (cover.getType() == CoverType.TAG_COVER) {
                jl.setToolTipText("<html>Tag<br>" + size + "K");
            } else {
                jl.setToolTipText("<html>" + url.toString() + "<br>" + size + "K");
            }
            setSizeText(size + "K" + sType);
            setFoundText();
        }
        // set tooltip for previous and next track
        try {
            int indexPrevious = index + 1;
            if (indexPrevious > alCovers.size() - 1) {
                indexPrevious = 0;
            }
            final URL urlPrevious = alCovers.get(indexPrevious).getURL();
            if (urlPrevious != null) {
                jbPrevious.setToolTipText(
                        "<html>" + Messages.getString("CoverView.4") + "<br>" + urlPrevious.toString() + "</html>");
            }
            int indexNext = index - 1;
            if (indexNext < 0) {
                indexNext = alCovers.size() - 1;
            }
            final URL urlNext = alCovers.get(indexNext).getURL();
            if (urlNext != null) {
                jbNext.setToolTipText(
                        "<html>" + Messages.getString("CoverView.5") + "<br>" + urlNext.toString() + "</html>");
            }
        } catch (final Exception e) { // the url code can throw out of bounds
            // exception for unknown reasons so check it
            Log.debug("jl=" + jl + " url={{" + url + "}}");
            Log.error(e);
        }
        if (getComponentCount() > 0) {
            removeAll();
        }
        if (includeControls) {
            add(jpControl, "grow,wrap");
        }
        // Invert the mirrow option when clicking on the cover
        jl.addMouseListener(new JajukMouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (!(cover.getType().equals(CoverType.NO_COVER))) {
                    boolean isMirrowed = includeControls ? Conf.getBoolean(Const.CONF_COVERS_MIRROW_COVER)
                            : Conf.getBoolean(Const.CONF_COVERS_MIRROW_COVER_FS_MODE);
                    // Normal cover view
                    if (includeControls) {
                        Conf.setProperty(Const.CONF_COVERS_MIRROW_COVER, !isMirrowed + "");
                    } else {
                        // Full screen mode
                        Conf.setProperty(Const.CONF_COVERS_MIRROW_COVER_FS_MODE, !isMirrowed + "");
                    }
                    ObservationManager.notify(new JajukEvent(JajukEvents.COVER_NEED_REFRESH));
                    ObservationManager.notify(new JajukEvent(JajukEvents.PARAMETERS_CHANGE));
                }
            }
        });
        add(jl, "center,wrap");
        // make sure the image is repainted to avoid overlapping covers
        CoverView.this.revalidate();
        CoverView.this.repaint();
    }

    /**
     * Refresh default cover thumb (used in catalog view).
     * 
     * @param cover 
     */
    private void refreshThumbs(final Cover cover) {
        if (dirReference == null) {
            Log.warn("Cannot refresh thumbnails without reference directory");
            return;
        }
        // refresh thumbs
        try {
            for (int size = 50; size <= 300; size += 50) {
                final Album album = dirReference.getFiles().iterator().next().getTrack().getAlbum();
                final File fThumb = ThumbnailManager.getThumbBySize(album, size);
                ThumbnailManager.createThumbnail(cover.getFile(), fThumb, size);
            }
        } catch (final Exception ex) {
            Log.error(24, ex);
        }
    }

    /**
    * Set the cover Found text.
    */
    private void setFoundText() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                // make sure not to display negative indexes
                int i = getCoverNumber() - index;
                if (i < 0) {
                    Log.debug("Negative cover index: " + i);
                    i = 0;
                }
                jlFound.setText(i + "/" + getCoverNumber());
            }
        });
    }

    /**
     * Set the cover Found text.
     * 
     * @param sFound specified text
     */
    private void setFoundText(final String sFound) {
        if (sFound != null) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    jlFound.setText(sFound);
                }
            });
        }
    }

    /**
     * Set the cover size text.
     * 
     * @param sSize 
     */
    private void setSizeText(final String sSize) {
        if (sSize != null) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    jlSize.setText(sSize);
                }
            });
        }
    }

    /**
     * Gets the current image.
     * 
     * @return the current image
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws InterruptedException the interrupted exception
     * @throws JajukException the jajuk exception
     */
    public Image getCurrentImage() throws IOException, InterruptedException, JajukException {
        if (alCovers.size() > 0) {
            return alCovers.get(0).getImage();
        }
        return CoverView.nocover.getImage();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.ui.Observer#update(java.lang.String)
     */
    @Override
    public void update(final JajukEvent event) {
        final JajukEvents subject = event.getSubject();
        try {
            listLock.lock();
            // When receiving this event, check if we should change the cover or
            // not (we don't change cover if playing another track of the same album
            // except if option shuffle cover is set)
            if (JajukEvents.FILE_LAUNCHED.equals(subject)) {
                updateFileLaunched(event);
            } else if (JajukEvents.ZERO.equals(subject) || JajukEvents.WEBRADIO_LAUNCHED.equals(subject)
                    || JajukEvents.PLAYER_STOP.equals(subject)) {
                reset();
            } else if (JajukEvents.COVER_NEED_REFRESH.equals(subject) && !QueueModel.isPlayingRadio()) {
                refreshCovers(true);
                displayCurrentCover();
            }
        } catch (final IOException e) {
            Log.error(e);
        } finally {
            listLock.unlock();
        }
    }

    /**
     * Update stop or web radio launched.
     * 
     */
    private void reset() {
        // Ignore this event if a reference file has been set
        if (fileReference != null) {
            return;
        }
        setFoundText("");
        setSizeText("");
        alCovers.clear();
        alCovers.add(CoverView.nocover); // add the default cover
        index = 0;
        displayCurrentCover();
        dirReference = null;
        // Force cover to reload at next track
        bForceCoverReload = true;
        // disable commands
        enableCommands(false);
    }

    /**
     * Update file launched.
     * 
     * @param event 
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void updateFileLaunched(final JajukEvent event) throws IOException {
        org.jajuk.base.File last = null;
        Properties details = event.getDetails();
        if (details != null) {
            StackItem item = (StackItem) details.get(Const.DETAIL_OLD);
            if (item != null) {
                last = item.getFile();
            }
        }
        // Ignore this event if a reference file has been set and if
        // this event has already been handled
        if ((fileReference != null) && (dirReference != null)) {
            return;
        }
        // if we are always in the same directory, just leave to
        // save cpu
        boolean dirChanged = last == null ? true
                : !last.getDirectory().equals(QueueModel.getPlayingFile().getDirectory());
        if (bForceCoverReload) {
            dirChanged = true;
        }
        refreshCovers(dirChanged);
        if (Conf.getBoolean(Const.CONF_COVERS_SHUFFLE)) {
            // Ignore this event if a reference file has been set
            if (fileReference != null) {
                return;
            }
            // choose a random cover
            index = (int) (Math.random() * alCovers.size() - 1);
        }
        displayCurrentCover();
        enableCommands(true);
    }

    /**
     * Convenient method to massively enable/disable this view buttons.
     * 
     * @param enable 
     */
    private void enableCommands(final boolean enable) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                jcbAccuracy.setEnabled(enable);
                jbDefault.setEnabled(enable);
                jbDelete.setEnabled(enable);
                jbNext.setEnabled(enable);
                jbPrevious.setEnabled(enable);
                jbSave.setEnabled(enable);
                jlFound.setVisible(enable);
                jlSize.setVisible(enable);
            }
        });
    }

    /**
     * Covers refreshing effective code
     * <p>
     * Must be called outside the EDT, contains network access
     * </p>.
     * 
     * @param dirChanged 
     * 
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void refreshCovers(boolean dirChanged) throws IOException {
        // Reset this flag
        bForceCoverReload = false;
        org.jajuk.base.File fCurrent = fileReference;
        // check if a file has been given for this cover view
        // if not, take current cover
        if (fCurrent == null) {
            fCurrent = QueueModel.getPlayingFile();
        }
        // no current cover and nothing playing
        if (fCurrent == null) {
            dirReference = null;
        } else {
            // store this dir
            dirReference = fCurrent.getDirectory();
        }
        if (dirReference == null) {
            alCovers.clear();
            alCovers.add(CoverView.nocover);
            index = 0;
            return;
        }
        if (fCurrent == null) {
            throw new IllegalArgumentException("Internal Error: Unexpected value, "
                    + "variable fCurrent should not be empty. dirReference: " + dirReference);
        }
        // We only need to refresh the other covers if the directory changed 
        // but we still clear tag-based covers even if directory didn't change
        // so the song-specific tag is taken into account. 
        Iterator<Cover> it = alCovers.iterator();
        while (it.hasNext()) {
            Cover cover = it.next();
            if (cover.getType() == CoverType.TAG_COVER) {
                it.remove();
            }
        }
        if (dirChanged) {
            // remove all existing covers
            alCovers.clear();
            // Search for local covers in all directories mapping
            // the current track to reach other devices covers and
            // display them together
            final Track trackCurrent = fCurrent.getTrack();
            final List<org.jajuk.base.File> alFiles = trackCurrent.getFiles();
            // Add any selected default cover
            String defaultCoverPath = trackCurrent.getAlbum().getStringValue(XML_ALBUM_SELECTED_COVER);
            if (StringUtils.isNotBlank(defaultCoverPath)) {
                File coverFile = new File(defaultCoverPath);
                if (coverFile.exists()) {
                    final Cover cover = new Cover(coverFile, CoverType.SELECTED_COVER);
                    // Avoid dups
                    if (!alCovers.contains(cover)) {
                        alCovers.add(cover);
                    }
                }
            }
            // list of files mapping the track
            for (final org.jajuk.base.File file : alFiles) {
                final Directory dirScanned = file.getDirectory();
                if (!dirScanned.getDevice().isMounted()) {
                    // if the device is not ready, just ignore it
                    continue;
                }
                // Now search for regular or standard local covers
                // null if none file found
                final java.io.File[] files = dirScanned.getFio().listFiles();
                for (int i = 0; (files != null) && (i < files.length); i++) {
                    // check size to avoid out of memory errors
                    if (files[i].length() > Const.MAX_COVER_SIZE * 1024) {
                        continue;
                    }
                    final JajukFileFilter filter = ImageFilter.getInstance();
                    if (filter.accept(files[i])) {
                        Cover cover = null;
                        if (UtilFeatures.isStandardCover(files[i])) {
                            cover = new Cover(files[i], CoverType.STANDARD_COVER);
                        } else {
                            cover = new Cover(files[i], CoverType.LOCAL_COVER);
                        }
                        if (!alCovers.contains(cover)) {
                            alCovers.add(cover);
                        }
                    }
                }
            }
            // Then we search for web covers online if max
            // connection errors number is not reached or if user
            // already managed to connect.
            // We also drop the query if user required none internet access
            if (Conf.getBoolean(Const.CONF_COVERS_AUTO_COVER)
                    && !Conf.getBoolean(Const.CONF_NETWORK_NONE_INTERNET_ACCESS)
                    && (CoverView.bOnceConnected || (CoverView.iErrorCounter < Const.STOP_TO_SEARCH))) {
                try {
                    final String sQuery = createQuery(fCurrent);
                    Log.debug("Query={{" + sQuery + "}}");
                    if (!sQuery.isEmpty()) {
                        // there is not enough information in tags
                        // for a web search
                        List<URL> alUrls;
                        alUrls = DownloadManager.getRemoteCoversList(sQuery);
                        CoverView.bOnceConnected = true;
                        // user managed once to connect to the web
                        if (alUrls.size() > Const.MAX_REMOTE_COVERS) {
                            // limit number of remote covers
                            alUrls = new ArrayList<URL>(alUrls.subList(0, Const.MAX_REMOTE_COVERS));
                        }
                        Collections.reverse(alUrls);
                        // set best results to be displayed first
                        final Iterator<URL> it2 = alUrls.iterator();
                        // add found covers
                        while (it2.hasNext()) {
                            // load each cover (pre-load or post-load)
                            // and stop if a signal has been emitted
                            final URL url = it2.next();
                            final Cover cover = new Cover(url, CoverType.REMOTE_COVER);
                            // Create a cover with given url ( image
                            // will be really downloaded when
                            // required if no preload)
                            if (!alCovers.contains(cover)) {
                                Log.debug("Found Cover: {{" + url.toString() + "}}");
                                alCovers.add(cover);
                            }
                        }
                    }
                } catch (final IOException e) {
                    Log.warn(e.getMessage());
                    // can occur in case of timeout or error during
                    // covers list download
                    CoverView.iErrorCounter++;
                    if (CoverView.iErrorCounter == Const.STOP_TO_SEARCH) {
                        Log.warn("Too many connection fails," + " stop to search for covers online");
                        InformationJPanel.getInstance().setMessage(Messages.getString("Error.030"),
                                InformationJPanel.MessageType.WARNING);
                    }
                } catch (final Exception e) {
                    Log.error(e);
                }
            }
        }
        // Check for tag covers 
        try {
            Tag tag = new Tag(fCurrent.getFIO(), false);
            List<Cover> tagCovers = tag.getCovers();
            // Reverse order of the found tag covers because we want best last
            // in alCovers and we want to keep tag order.
            Collections.reverse(tagCovers);
            for (Cover cover : tagCovers) {
                // Avoid dups
                if (!alCovers.contains(cover)) {
                    alCovers.add(cover);
                }
            }
        } catch (JajukException e1) {
            Log.error(e1);
        }
        if (alCovers.size() == 0) {// add the default cover if none
            // other cover has been found
            alCovers.add(CoverView.nocover);
        }
        Collections.sort(alCovers);
        Log.debug("Local cover list: {{" + alCovers + "}}");
        if (Conf.getBoolean(Const.CONF_COVERS_SHUFFLE)) {
            // choose a random cover
            index = (int) (Math.random() * alCovers.size());
        } else {
            index = alCovers.size() - 1;
            // current index points to the best available cover
        }
    }
}