/**
  * Manual refresh, displays a dialog.
  *
  * @param bAsk ask for refreshing type (deep or fast ?)
  * @param bAfterMove is this refresh done after a device location change ?
  * @param forcedDeep : override bAsk and force a deep refresh
  * @param dirsToRefresh : only refresh specified dirs, or all of them if null
  */
 public void manualRefresh(
     final boolean bAsk,
     final boolean bAfterMove,
     final boolean forcedDeep,
     List<Directory> dirsToRefresh) {
   int i = 0;
   try {
     i = prepareRefresh(bAsk);
     if (i == OPTION_REFRESH_CANCEL) {
       return;
     }
     bAlreadyRefreshing = true;
   } catch (JajukException je) {
     Messages.showErrorMessage(je.getCode());
     Log.debug(je);
     return;
   }
   try {
     reporter = new ManualDeviceRefreshReporter(this);
     reporter.startup();
     // clean old files up (takes a while)
     if (!bAfterMove) {
       cleanRemovedFiles(dirsToRefresh);
     }
     reporter.cleanupDone();
     // Actual refresh
     refreshCommand(((i == Device.OPTION_REFRESH_DEEP) || forcedDeep), true, dirsToRefresh);
     // cleanup logical items
     org.jajuk.base.Collection.cleanupLogical();
     // if it is a move, clean old files *after* the refresh
     if (bAfterMove) {
       cleanRemovedFiles(dirsToRefresh);
     }
     // notify views to refresh
     ObservationManager.notify(new JajukEvent(JajukEvents.DEVICE_REFRESH));
     // Commit collection at each refresh (can be useful if
     // application
     // is closed brutally with control-C or shutdown and that
     // exit hook has no time to perform commit).
     // But don't commit when any device is refreshing to avoid collisions.
     if (!DeviceManager.getInstance().isAnyDeviceRefreshing()) {
       try {
         org.jajuk.base.Collection.commit(SessionService.getConfFileByPath(Const.FILE_COLLECTION));
       } catch (final IOException e) {
         Log.error(e);
       }
     }
   } finally {
     // Do not let current reporter as a manual reporter because it would fail
     // in NPE with auto-refresh
     reporter = null;
     // Make sure to unlock refreshing
     bAlreadyRefreshing = false;
   }
 }
 /**
  * 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;
     }
   }
 }