private void cleanup(TvShow tvShow) {
    boolean dirty = false;
    if (!tvShow.isNewlyAdded()) {
      // check and delete all not found MediaFiles
      List<MediaFile> mediaFiles = new ArrayList<MediaFile>(tvShow.getMediaFiles());
      for (MediaFile mf : mediaFiles) {
        if (!mf.getFile().exists()) {
          tvShow.removeFromMediaFiles(mf);
          dirty = true;
        }
      }
      List<TvShowEpisode> episodes = new ArrayList<TvShowEpisode>(tvShow.getEpisodes());
      for (TvShowEpisode episode : episodes) {
        mediaFiles = new ArrayList<MediaFile>(episode.getMediaFiles());
        for (MediaFile mf : mediaFiles) {
          if (!mf.getFile().exists()) {
            episode.removeFromMediaFiles(mf);
            dirty = true;
          }
        }
        // lets have a look if there is at least one video file for this episode
        List<MediaFile> mfs = episode.getMediaFiles(MediaFileType.VIDEO);
        if (mfs.size() == 0) {
          tvShow.removeEpisode(episode);
          dirty = true;
        }
      }
    }

    if (dirty) {
      tvShow.saveToDb();
    }
  }
 @Override
 public void mouseClicked(MouseEvent arg0) {
   int col = tableFiles.columnAtPoint(arg0.getPoint());
   if (col == 0) {
     int row = tableFiles.rowAtPoint(arg0.getPoint());
     row = tableFiles.convertRowIndexToModel(row);
     MediaFile mf = mediaFileEventList.get(row);
     // open the video file in the desired player
     if (mf.isVideo()) {
       try {
         TmmUIHelper.openFile(mf.getFile());
       } catch (Exception e) {
         LOGGER.error("open file", e);
         MessageManager.instance.pushMessage(
             new Message(
                 MessageLevel.ERROR,
                 mf,
                 "message.erroropenfile",
                 new String[] {":", e.getLocalizedMessage()}));
       }
     }
     // open the graphic in the lightbox
     if (mf.isGraphic()) {
       MainWindow.getActiveInstance()
           .createLightbox(mf.getPath() + File.separator + mf.getFilename(), "");
     }
   }
 }
  /*
   * update a single TV show
   */
  private void updateTvShows() {
    // one thread here - more threads killed the UI
    initThreadPool(1, "update");

    for (File tvShowFolder : tvShowFolders) {
      // check if the tv show dir is accessible
      File[] filesInDatasourceRoot = tvShowFolder.getParentFile().listFiles();
      if (filesInDatasourceRoot == null || filesInDatasourceRoot.length == 0) {
        LOGGER.warn("TvShow folder not available/empty " + tvShowFolder);
        MessageManager.instance.pushMessage(
            new Message(
                MessageLevel.ERROR,
                "update.datasource",
                "update.datasource.unavailable",
                new String[] {tvShowFolder.getParent()}));
        continue;
      }

      if (tvShowFolder.isDirectory()) {
        submitTask(new FindTvShowTask(tvShowFolder, tvShowFolder.getParent()));
      }
    }

    waitForCompletionOrCancel();

    // cleanup
    setTaskName(BUNDLE.getString("update.cleanup"));
    setTaskDescription(null);
    setProgressDone(0);
    setWorkUnits(0);
    publishState();

    LOGGER.info("removing orphaned movies/files...");
    for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) {
      if (cancel) {
        break;
      }
      TvShow tvShow = tvShowList.getTvShows().get(i);

      // check only Tv shows matching datasource
      if (!tvShowFolders.contains(new File(tvShow.getPath()))) {
        continue;
      }

      // check and delete all not found MediaFiles
      cleanup(tvShow);
    }

    // start MI
    setTaskName(BUNDLE.getString("update.mediainfo"));
    publishState();

    initThreadPool(1, "mediainfo");
    LOGGER.info("getting Mediainfo...");
    for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) {
      if (cancel) {
        break;
      }
      TvShow tvShow = tvShowList.getTvShows().get(i);

      // check only Tv shows matching datasource
      if (!tvShowFolders.contains(new File(tvShow.getPath()))) {
        continue;
      }

      gatherMediaInformationForUngatheredMediaFiles(tvShow);
    }

    waitForCompletionOrCancel();

    if (cancel) {
      return;
    }

    // build up the image cache
    if (Globals.settings.getTvShowSettings().isBuildImageCacheOnImport()) {
      List<File> imageFiles = new ArrayList<File>();
      for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) {
        if (cancel) {
          break;
        }
        TvShow tvShow = tvShowList.getTvShows().get(i);

        // check only Tv shows matching datasource
        if (!tvShowFolders.contains(new File(tvShow.getPath()))) {
          continue;
        }

        for (MediaFile mf : new ArrayList<MediaFile>(tvShow.getMediaFiles())) {
          if (mf.isGraphic()) {
            imageFiles.add(mf.getFile());
          }
        }
        for (TvShowEpisode episode : tvShow.getEpisodes()) {
          for (MediaFile mf : new ArrayList<MediaFile>(episode.getMediaFiles())) {
            if (mf.isGraphic()) {
              imageFiles.add(mf.getFile());
            }
          }
        }
      }

      ImageCacheTask task = new ImageCacheTask(imageFiles);
      TmmTaskManager.getInstance().addUnnamedTask(task);
    }
  }
  /*
   * update one or more datasources
   */
  private void updateDatasource() {
    List<File> imageFiles = new ArrayList<File>();

    for (String path : dataSources) {
      File[] dirs = new File(path).listFiles();
      // check whether the path is accessible (eg disconnected shares)
      if (dirs == null || dirs.length == 0) {
        // error - continue with next datasource
        LOGGER.warn("Datasource not available/empty " + path);
        MessageManager.instance.pushMessage(
            new Message(
                MessageLevel.ERROR,
                "update.datasource",
                "update.datasource.unavailable",
                new String[] {path}));
        continue;
      }

      // one thread here - more threads killed the UI
      initThreadPool(1, "update");

      for (File subdir : dirs) {
        if (cancel) {
          break;
        }

        String directoryName = subdir.getName();
        // check against unwanted dirs
        if (skipFolders.contains(directoryName.toUpperCase())
            || directoryName.matches(skipFoldersRegex)
            || TvShowModuleManager.TV_SHOW_SETTINGS
                .getTvShowSkipFolders()
                .contains(subdir.getAbsolutePath())) {
          LOGGER.info("ignoring directory " + directoryName);
          continue;
        }

        // check this dir as TV show dir
        if (subdir.isDirectory()) {
          // check if there is a .tmmignore in this directory
          File tmmIgnore = new File(subdir, ".tmmignore");
          if (!tmmIgnore.exists()) {
            submitTask(new FindTvShowTask(subdir, path));
          }
        }

        // video FILE in DS root - not supported!
        if (subdir.isFile()
            && Globals.settings
                .getVideoFileType()
                .contains("." + FilenameUtils.getExtension(subdir.getName()))) {
          MessageManager.instance.pushMessage(
              new Message(
                  MessageLevel.ERROR,
                  "update.datasource",
                  "update.datasource.episodeinroot",
                  new String[] {subdir.getName()}));
        }
      }

      waitForCompletionOrCancel();
      if (cancel) {
        break;
      }

      // cleanup
      setTaskName(BUNDLE.getString("update.cleanup"));
      setTaskDescription(null);
      setProgressDone(0);
      setWorkUnits(0);
      publishState();
      LOGGER.info("removing orphaned tv shows/files...");
      for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) {
        if (cancel) {
          break;
        }
        TvShow tvShow = tvShowList.getTvShows().get(i);
        if (!new File(path).equals(new File(tvShow.getDataSource()))) {
          // check only Tv shows matching datasource
          continue;
        }

        File tvShowDir = new File(tvShow.getPath());
        if (!tvShowDir.exists()) {
          tvShowList.removeTvShow(tvShow);
        } else {
          // do a cleanup
          cleanup(tvShow);
        }
      }

      // mediainfo
      setTaskName(BUNDLE.getString("update.mediainfo"));
      publishState();

      initThreadPool(1, "mediainfo");
      LOGGER.info("getting Mediainfo...");
      for (int i = tvShowList.getTvShows().size() - 1; i >= 0; i--) {
        if (cancel) {
          break;
        }
        TvShow tvShow = tvShowList.getTvShows().get(i);
        if (!new File(path).equals(new File(tvShow.getDataSource()))) {
          // check only Tv shows matching datasource
          continue;
        }

        gatherMediaInformationForUngatheredMediaFiles(tvShow);
      }

      waitForCompletionOrCancel();
      if (cancel) {
        break;
      }

      // build image cache on import
      if (Globals.settings.getTvShowSettings().isBuildImageCacheOnImport()) {
        for (TvShow tvShow : new ArrayList<TvShow>(tvShowList.getTvShows())) {
          if (!new File(path).equals(new File(tvShow.getDataSource()))) {
            continue;
          }
          for (MediaFile mf : new ArrayList<MediaFile>(tvShow.getMediaFiles())) {
            if (mf.isGraphic()) {
              imageFiles.add(mf.getFile());
            }
          }
          for (TvShowEpisode episode : tvShow.getEpisodes()) {
            for (MediaFile mf : new ArrayList<MediaFile>(episode.getMediaFiles())) {
              if (mf.isGraphic()) {
                imageFiles.add(mf.getFile());
              }
            }
          }
        }
      }
    }

    if (cancel) {
      return;
    }

    if (imageFiles.size() > 0) {
      ImageCacheTask task = new ImageCacheTask(imageFiles);
      TmmTaskManager.getInstance().addUnnamedTask(task);
    }

    if (Globals.settings.getTvShowSettings().getSyncTrakt()) {
      TmmTask task = new SyncTraktTvTask(false, false, true, true);
      TmmTaskManager.getInstance().addUnnamedTask(task);
    }
  }
  private static String generateName(
      String template, TvShow tvShow, MediaFile mf, boolean forFile) {
    String filename = "";
    List<TvShowEpisode> eps = TvShowList.getInstance().getTvEpisodesByFile(tvShow, mf.getFile());
    if (eps == null || eps.size() == 0) {
      // this should not happen, but unluckily ODB does it sometimes; try a second time to get the
      // episode
      try {
        Thread.sleep(250);
      } catch (Exception ex) {
      }
      eps = TvShowList.getInstance().getTvEpisodesByFile(tvShow, mf.getFile());
    }
    if (eps == null || eps.size() == 0) {
      return "";
    }

    if (StringUtils.isBlank(template)) {
      filename = createDestination(SETTINGS.getRenamerFilename(), tvShow, eps);
    } else {
      filename = createDestination(template, tvShow, eps);
    }

    // since we can use this method for folders too, use the next options solely for files
    if (forFile) {
      if (mf.getType().equals(MediaFileType.THUMB)) {
        if (SETTINGS.isUseRenamerThumbPostfix()) {
          filename = filename + "-thumb";
        }
        // else let the filename as is
      }
      if (mf.getType().equals(MediaFileType.FANART)) {
        filename = filename + "-fanart";
      }
      if (mf.getType().equals(MediaFileType.TRAILER)) {
        filename = filename + "-trailer";
      }
      if (mf.getType().equals(MediaFileType.VIDEO_EXTRA)) {
        String name = mf.getBasename();
        Pattern p = Pattern.compile("(?i).*([ _.-]extras[ _.-]).*");
        Matcher m = p.matcher(name);
        if (m.matches()) {
          name = name.substring(m.end(1)); // everything behind
        }
        // if not, MF must be within /extras/ folder - use name 1:1
        filename = filename + "-extras-" + name;
      }
      if (mf.getType().equals(MediaFileType.SUBTITLE)) {
        List<MediaFileSubtitle> subtitles = mf.getSubtitles();
        if (subtitles != null && subtitles.size() > 0) {
          MediaFileSubtitle mfs = mf.getSubtitles().get(0);
          if (mfs != null) {
            if (!mfs.getLanguage().isEmpty()) {
              filename = filename + "." + mfs.getLanguage();
            }
            if (mfs.isForced()) {
              filename = filename + ".forced";
            }
          } else {
            // TODO: meh, we didn't have an actual MF yet - need to parse filename ourselves (like
            // movie). But with a recent scan of files/DB this
            // should not occur.
          }
        }
      }
    } // end forFile

    // ASCII replacement
    if (SETTINGS.isAsciiReplacement()) {
      filename = StrgUtils.convertToAscii(filename, false);
    }

    filename = filename + "." + mf.getExtension(); // readd original extension

    return filename;
  }
  /**
   * Renames a MediaFiles<br>
   * gets all episodes of it, creates season folder, updates MFs & DB
   *
   * @param mf the MediaFile
   * @param show the tvshow (only needed for path)
   */
  public static void renameMediaFile(MediaFile mf, TvShow show) {
    // #######################################################
    // Assumption: all multi-episodes share the same season!!!
    // #######################################################

    List<TvShowEpisode> eps = TvShowList.getInstance().getTvEpisodesByFile(show, mf.getFile());
    if (eps == null || eps.size() == 0) {
      // this should not happen, but unluckily ODB does it sometimes; try a second time to get the
      // episode
      try {
        Thread.sleep(250);
      } catch (Exception e) {
      }
      eps = TvShowList.getInstance().getTvEpisodesByFile(show, mf.getFile());
    }
    if (eps == null || eps.size() == 0) {
      // FIXME: workaround for r1972
      // when moving video file, all NFOs get deleted and a new gets created.
      // so this OLD NFO is not found anylonger - just delete it
      if (mf.getType() == MediaFileType.NFO) {
        FileUtils.deleteQuietly(mf.getFile());
        return;
      }

      LOGGER.warn("No episodes found for file '" + mf.getFilename() + "' - skipping");
      return;
    }

    // get first, for isDisc and season
    TvShowEpisode ep = eps.get(0);

    // test access rights or return
    LOGGER.debug(
        "testing file S:"
            + ep.getSeason()
            + " E:"
            + ep.getEpisode()
            + " MF:"
            + mf.getFile().getAbsolutePath());
    File f = mf.getFile();
    boolean testRenameOk = false;
    for (int i = 0; i < 5; i++) {
      testRenameOk = f.renameTo(f); // haahaa, try to rename to itself :P
      if (testRenameOk) {
        break; // ok it worked, step out
      }
      try {
        if (!f.exists()) {
          LOGGER.debug("Hmmm... file " + f + " does not even exists; delete from DB");
          // delete from MF
          for (TvShowEpisode e : eps) {
            e.removeFromMediaFiles(mf);
            e.saveToDb();
            e.writeNFO();
          }
          return;
        }
        LOGGER.debug("rename did not work - sleep a while and try again...");
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        LOGGER.warn("I'm so excited - could not sleep");
      }
    }
    if (!testRenameOk) {
      LOGGER.warn("File " + mf.getFile().getAbsolutePath() + " is not accessible!");
      MessageManager.instance.pushMessage(
          new Message(MessageLevel.ERROR, mf.getFilename(), "message.renamer.failedrename"));
      return;
    }

    // create SeasonDir
    // String seasonName = "Season " + String.valueOf(ep.getSeason());
    String seasonName = generateSeasonDir(SETTINGS.getRenamerSeasonFoldername(), ep);
    File seasonDir = null;
    if (StringUtils.isNotBlank(seasonName)) {
      seasonDir = new File(show.getPath(), seasonName);
      if (!seasonDir.exists()) {
        seasonDir.mkdir();
      }
    } else {
      seasonDir = new File(show.getPath());
    }

    // rename epFolder accordingly
    if (ep.isDisc() || mf.isDiscFile()) {
      // \Season 1\S01E02E03\VIDEO_TS\VIDEO_TS.VOB
      // ........ \epFolder \disc... \ file
      File disc = mf.getFile().getParentFile();
      File epFolder = disc.getParentFile();

      // sanity check
      if (!disc.getName().equalsIgnoreCase("BDMV")
          && !disc.getName().equalsIgnoreCase("VIDEO_TS")) {
        LOGGER.error(
            "Episode is labeled as 'on BD/DVD', but structure seems not to match. Better exit and do nothing... o_O");
        return;
      }

      String newFoldername =
          FilenameUtils.getBaseName(generateFolderename(show, mf)); // w/o extension
      if (newFoldername != null && !newFoldername.isEmpty()) {
        File newEpFolder = new File(seasonDir + File.separator + newFoldername);
        File newDisc = new File(newEpFolder + File.separator + disc.getName()); // old disc name

        try {
          // if (!epFolder.equals(newEpFolder)) {
          if (!epFolder.getAbsolutePath().equals(newEpFolder.getAbsolutePath())) {
            boolean ok = false;
            try {
              ok = Utils.moveDirectorySafe(epFolder, newEpFolder);
            } catch (Exception e) {
              LOGGER.error(e.getMessage());
              MessageManager.instance.pushMessage(
                  new Message(
                      MessageLevel.ERROR,
                      epFolder.getName(),
                      "message.renamer.failedrename",
                      new String[] {":", e.getLocalizedMessage()}));
            }
            if (ok) {
              // iterate over all EPs & MFs and fix new path
              LOGGER.debug("updating *all* MFs for new path -> " + newEpFolder);
              for (TvShowEpisode e : eps) {
                e.updateMediaFilePath(disc, newDisc);
                e.setPath(newEpFolder.getPath());
                e.saveToDb();
                e.writeNFO();
              }
            }
            // and cleanup
            cleanEmptyDir(epFolder);
          } else {
            // old and new folder are equal, do nothing
          }
        } catch (Exception e) {
          LOGGER.error("error moving video file " + disc.getName() + " to " + newFoldername, e);
          MessageManager.instance.pushMessage(
              new Message(
                  MessageLevel.ERROR,
                  mf.getFilename(),
                  "message.renamer.failedrename",
                  new String[] {":", e.getLocalizedMessage()}));
        }
      }
    } // end isDisc
    else {
      MediaFile newMF = new MediaFile(mf); // clone MF
      if (mf.getType().equals(MediaFileType.TRAILER)) {
        // move trailer into separate dir - not supported by XBMC
        File sample = new File(seasonDir, "sample");
        if (!sample.exists()) {
          sample.mkdir();
        }
        seasonDir = sample; // change directory storage
      }
      String filename = generateFilename(show, mf);
      LOGGER.debug("new filename should be " + filename);
      if (filename != null && !filename.isEmpty()) {
        File newFile = new File(seasonDir, filename);

        try {
          // if (!mf.getFile().equals(newFile)) {
          if (!mf.getFile().getAbsolutePath().equals(newFile.getAbsolutePath())) {
            File oldMfFile = mf.getFile();
            boolean ok = false;
            try {
              ok = Utils.moveFileSafe(oldMfFile, newFile);
            } catch (Exception e) {
              LOGGER.error(e.getMessage());
              MessageManager.instance.pushMessage(
                  new Message(
                      MessageLevel.ERROR,
                      oldMfFile.getPath(),
                      "message.renamer.failedrename",
                      new String[] {":", e.getLocalizedMessage()}));
            }
            if (ok) {
              newMF.setPath(seasonDir.getAbsolutePath());
              newMF.setFilename(filename);
              // iterate over all EPs and delete old / set new MF
              for (TvShowEpisode e : eps) {
                e.removeFromMediaFiles(mf);
                e.addToMediaFiles(newMF);
                e.setPath(seasonDir.getAbsolutePath());
                e.saveToDb();
                e.writeNFO();
              }
            }
            // and cleanup
            cleanEmptyDir(oldMfFile.getParentFile());
          } else {
            // old and new file are equal, keep MF
          }
        } catch (Exception e) {
          LOGGER.error(
              "error moving video file " + mf.getFilename() + " to " + newFile.getPath(), e);
          MessageManager.instance.pushMessage(
              new Message(
                  MessageLevel.ERROR,
                  mf.getFilename(),
                  "message.renamer.failedrename",
                  new String[] {":", e.getLocalizedMessage()}));
        }
      }
    }
  }