/**
  * Walks through all directories and removes the ones for this device.
  *
  * @param dirsToRefresh list of the directory to refresh, null if all of them
  * @return true if there was any directory removed
  */
 private boolean cleanDirectories(List<Directory> dirsToRefresh) {
   boolean bChanges = false;
   List<Directory> dirs = null;
   if (dirsToRefresh == null) {
     dirs = DirectoryManager.getInstance().getDirectories();
   } else {
     // If one or more named directories are provided, not only clean them up but also their sub
     // directories
     dirs = new ArrayList<Directory>(dirsToRefresh);
     for (Directory dir : dirsToRefresh) {
       dirs.addAll(dir.getDirectoriesRecursively());
     }
   }
   for (final Directory dir : dirs) {
     if (!ExitService.isExiting()
         && dir.getDevice().equals(this)
         && dir.getDevice().isMounted()
         && !dir.getFio().exists()) {
       // note that associated files are removed too
       DirectoryManager.getInstance().removeDirectory(dir.getID());
       Log.debug("Removed: " + dir);
       bChanges = true;
     }
   }
   return bChanges;
 }
 /**
  * Walk through tall Files and remove the ones for the current device.
  *
  * @param dirsToRefresh list of the directory to refresh, null if all of them
  * @return true if there was any file removed.
  */
 private boolean cleanFiles(List<Directory> dirsToRefresh) {
   boolean bChanges = false;
   final List<org.jajuk.base.File> files = FileManager.getInstance().getFiles();
   for (final org.jajuk.base.File file : files) {
     // check if it is a file located inside refreshed directory
     if (dirsToRefresh != null) {
       boolean checkIt = false;
       for (Directory directory : dirsToRefresh) {
         if (file.hasAncestor(directory)) {
           checkIt = true;
         }
       }
       // This item is not in given directories, just continue
       if (!checkIt) {
         continue;
       }
     }
     if (!ExitService.isExiting()
         && file.getDirectory().getDevice().equals(this)
         && file.isReady()
         &&
         // Remove file if it doesn't exist any more or if it is a iTunes
         // file (useful for jajuk < 1.4)
         (!file.getFIO().exists() || file.getName().startsWith("._"))) {
       FileManager.getInstance().removeFile(file);
       Log.debug("Removed: " + file);
       bChanges = true;
       if (reporter != null) {
         reporter.notifyFileOrPlaylistDropped();
       }
     }
   }
   return bChanges;
 }
 /**
  * Walk through all Playlists and remove the ones for the current device.
  *
  * @param dirsToRefresh list of the directory to refresh, null if all of them
  * @return true if there was any playlist removed
  */
 private boolean cleanPlaylist(List<Directory> dirsToRefresh) {
   boolean bChanges = false;
   final List<Playlist> plfiles = PlaylistManager.getInstance().getPlaylists();
   for (final Playlist plf : plfiles) {
     // check if it is a playlist located inside refreshed directory
     if (dirsToRefresh != null) {
       boolean checkIt = false;
       for (Directory directory : dirsToRefresh) {
         if (plf.hasAncestor(directory)) {
           checkIt = true;
         }
       }
       // This item is not in given directories, just continue
       if (!checkIt) {
         continue;
       }
     }
     if (!ExitService.isExiting()
         && plf.getDirectory().getDevice().equals(this)
         && plf.isReady()
         && !plf.getFIO().exists()) {
       PlaylistManager.getInstance().removeItem(plf);
       Log.debug("Removed: " + plf);
       if (reporter != null) {
         reporter.notifyFileOrPlaylistDropped();
       }
       bChanges = true;
     }
   }
   return bChanges;
 }
 /**
  * Scan recursively.
  *
  * @param dir top directory to scan
  * @param bDeepScan whether we want to perform a deep scan (read tags again)
  */
 private void scanRecursively(final Directory dir, final boolean bDeepScan) {
   dir.scan(bDeepScan, reporter);
   if (reporter != null) {
     reporter.updateState(dir);
   }
   final File[] files = dir.getFio().listFiles(UtilSystem.getDirFilter());
   if (files != null) {
     for (final File element : files) {
       // Leave ASAP if exit request
       if (ExitService.isExiting()) {
         return;
       }
       final Directory subDir =
           DirectoryManager.getInstance().registerDirectory(element.getName(), dir, this);
       scanRecursively(subDir, bDeepScan);
     }
   }
 }
 /**
  * The refresh itself.
  *
  * @param bDeepScan whether it is a deep refresh request or only fast
  * @param bManual whether it is a manual refresh or auto
  * @param dirsToRefresh list of the directory to refresh, null if all of them
  * @return true if some changes occurred in device
  */
 boolean refreshCommand(
     final boolean bDeepScan, final boolean bManual, List<Directory> dirsToRefresh) {
   try {
     // Check if this device is mounted (useful when called by
     // automatic refresh)
     if (!isMounted()) {
       return false;
     }
     // Check that device is still available
     boolean readyToMount = checkDevice();
     if (!readyToMount) {
       return false;
     }
     bAlreadyRefreshing = true;
     // reporter is already set in case of manual refresh
     if (reporter == null) {
       reporter = new RefreshReporter(this);
     }
     // Notify the reporter of the actual refresh startup
     reporter.refreshStarted();
     lDateLastRefresh = System.currentTimeMillis();
     // check Jajuk is not exiting because a refresh cannot start in
     // this state
     if (ExitService.isExiting()) {
       return false;
     }
     int iNbFilesBeforeRefresh = FileManager.getInstance().getElementCount();
     int iNbDirsBeforeRefresh = DirectoryManager.getInstance().getElementCount();
     int iNbPlaylistsBeforeRefresh = PlaylistManager.getInstance().getElementCount();
     if (bDeepScan && Log.isDebugEnabled()) {
       Log.debug("Starting refresh of device : " + this);
     }
     // Create a directory for device itself and scan files to allow
     // files at the root of the device
     final Directory top = DirectoryManager.getInstance().registerDirectory(this);
     if (!getDirectories().contains(top)) {
       addDirectory(top);
     }
     // Start actual scan
     List<Directory> dirs = null;
     if (dirsToRefresh == null) {
       // No directory specified ? refresh the top directory
       dirs = new ArrayList<Directory>(1);
       dirs.add(top);
     } else {
       dirs = dirsToRefresh;
     }
     for (Directory dir : dirs) {
       scanRecursively(dir, bDeepScan);
     }
     // Force a GUI refresh if new files or directories discovered or have been
     // removed
     if (((FileManager.getInstance().getElementCount() - iNbFilesBeforeRefresh) != 0)
         || ((DirectoryManager.getInstance().getElementCount() - iNbDirsBeforeRefresh) != 0)
         || ((PlaylistManager.getInstance().getElementCount() - iNbPlaylistsBeforeRefresh) != 0)) {
       return true;
     }
     return false;
   } catch (final Exception e) {
     // and regular ones logged
     Log.error(e);
     return false;
   } finally {
     // make sure to unlock refreshing even if an error occurred
     bAlreadyRefreshing = false;
     // reporter is null if mount is not mounted due to early return
     if (reporter != null) {
       // Notify the reporter of the actual refresh startup
       reporter.done();
       // Reset the reporter as next time, it could be another type
       reporter = null;
     }
   }
 }
 /**
  * Synchronize a device with another one (unidirectional).
  *
  * @param dSrc
  * @param dest
  * @return nb of created files
  */
 private int synchronizeUnidirectonal(final Device dSrc, final Device dest) {
   final Set<Directory> hsSourceDirs = new HashSet<Directory>(100);
   // contains paths ( relative to device) of desynchronized dirs
   final Set<String> hsDesynchroPaths = new HashSet<String>(10);
   final Set<Directory> hsDestDirs = new HashSet<Directory>(100);
   int iNbCreatedFiles = 0;
   List<Directory> dirs = DirectoryManager.getInstance().getDirectories();
   for (Directory dir : dirs) {
     if (dir.getDevice().equals(dSrc)) {
       // don't take desynchronized dirs into account
       if (dir.getBooleanValue(Const.XML_DIRECTORY_SYNCHRONIZED)) {
         hsSourceDirs.add(dir);
       } else {
         hsDesynchroPaths.add(dir.getRelativePath());
       }
     }
   }
   for (Directory dir : dirs) {
     if (dir.getDevice().equals(dest)) {
       if (dir.getBooleanValue(Const.XML_DIRECTORY_SYNCHRONIZED)) {
         // don't take desynchronized dirs into account
         hsDestDirs.add(dir);
       } else {
         hsDesynchroPaths.add(dir.getRelativePath());
       }
     }
   }
   // handle known extensions and image files
   final FileFilter filter =
       new JajukFileFilter(
           false,
           new JajukFileFilter[] {KnownTypeFilter.getInstance(), ImageFilter.getInstance()});
   for (Directory dir : hsSourceDirs) {
     // give a chance to exit during sync
     if (ExitService.isExiting()) {
       return iNbCreatedFiles;
     }
     boolean bNeedCreate = true;
     final String sPath = dir.getRelativePath();
     // check the directory on source is not desynchronized. If it
     // is, leave without checking files
     if (hsDesynchroPaths.contains(sPath)) {
       continue;
     }
     for (Directory dir2 : hsDestDirs) {
       if (dir2.getRelativePath().equals(sPath)) {
         // directory already exists on this device
         bNeedCreate = false;
         break;
       }
     }
     // create it if needed
     final File fileNewDir = new File(new StringBuilder(dest.getUrl()).append(sPath).toString());
     if (bNeedCreate && !fileNewDir.mkdirs()) {
       Log.warn("Could not create directory " + fileNewDir);
     }
     // synchronize files
     final File fileSrc = new File(new StringBuilder(dSrc.getUrl()).append(sPath).toString());
     final File[] fSrcFiles = fileSrc.listFiles(filter);
     if (fSrcFiles != null) {
       for (final File element : fSrcFiles) {
         File[] filesArray = fileNewDir.listFiles(filter);
         if (filesArray == null) {
           // fileNewDir is not a directory or an error occurred (
           // read/write right ? )
           continue;
         }
         final List<File> files = Arrays.asList(filesArray);
         // Sort so files are copied in the filesystem order
         Collections.sort(files);
         boolean bNeedCopy = true;
         for (final File element2 : files) {
           if (element.getName().equalsIgnoreCase(element2.getName())) {
             bNeedCopy = false;
           }
         }
         if (bNeedCopy) {
           try {
             UtilSystem.copyToDir(element, fileNewDir);
             iNbCreatedFiles++;
             lVolume += element.length();
             InformationJPanel.getInstance()
                 .setMessage(
                     new StringBuilder(Messages.getString("Device.41"))
                         .append(dSrc.getName())
                         .append(',')
                         .append(dest.getName())
                         .append(Messages.getString("Device.42"))
                         .append(element.getAbsolutePath())
                         .append("]")
                         .toString(),
                     InformationJPanel.MessageType.INFORMATIVE);
           } catch (final JajukException je) {
             Messages.showErrorMessage(je.getCode(), element.getAbsolutePath());
             Messages.showErrorMessage(27);
             Log.error(je);
             return iNbCreatedFiles;
           } catch (final Exception e) {
             Messages.showErrorMessage(20, element.getAbsolutePath());
             Messages.showErrorMessage(27);
             Log.error(20, "{{" + element.getAbsolutePath() + "}}", e);
             return iNbCreatedFiles;
           }
         }
       }
     }
   }
   return iNbCreatedFiles;
 }