/** Saves a playlist. */
  public void exportM3U(Playlist playlist) {

    if (playlist == null) {
      return;
    }

    String suggestedName = CommonUtils.convertFileName(playlist.getName());

    // get the user to select a new one.... avoid FrostWire installation folder.
    File suggested;
    File suggestedDirectory = FileChooserHandler.getLastInputDirectory();
    if (suggestedDirectory.equals(CommonUtils.getCurrentDirectory())) {
      suggestedDirectory = new File(CommonUtils.getUserHomeDir(), "Desktop");
    }

    suggested = new File(suggestedDirectory, suggestedName + ".m3u");

    File selFile =
        FileChooserHandler.getSaveAsFile(
            GUIMediator.getAppFrame(),
            I18nMarker.marktr("Save Playlist As"),
            suggested,
            new PlaylistListFileFilter());

    // didn't select a file?  nothing we can do.
    if (selFile == null) {
      return;
    }

    // if the file already exists and not the one just opened, ask if it should be
    //  overwritten.
    // TODO: this should be handled in the jfilechooser
    if (selFile.exists()) {
      DialogOption choice =
          GUIMediator.showYesNoMessage(
              I18n.tr(
                  "Warning: a file with the name {0} already exists in the folder. Overwrite this file?",
                  selFile.getName()),
              QuestionsHandler.PLAYLIST_OVERWRITE_OK,
              DialogOption.NO);
      if (choice != DialogOption.YES) return;
    }

    String path = selFile.getPath();
    try {
      path = FileUtils.getCanonicalPath(selFile);
    } catch (IOException ignored) {
      // LOG.warn("unable to get canonical path for file: " + selFile, ignored);
    }
    // force m3u on the end.
    if (!path.toLowerCase().endsWith(".m3u")) path += ".m3u";

    // create a new thread to handle saving the playlist to disk
    saveM3U(playlist, path);
  }
  /**
   * Constructs the file system using the given BTData & hash information. If any of the information
   * is malformed, throws a ValueException.
   *
   * @param data contains all the data about a .torrent file
   * @param numHashes number of pieces the torrent was divided into
   * @param pieceLength size of divided up torrent file
   * @param infoHash a string of alphanumeric characters in the .torrent file that the client uses
   *     to verify the data that is being transferred
   * @throws ValueException
   */
  TorrentFileSystem(BTData data, int numHashes, long pieceLength, byte[] infoHash)
      throws IOException {
    // name of the torrent, also specifying the directory under which to save the torrents.
    _name = CommonUtils.convertFileName(data.getName());

    // we need to check the name of the torrent, security risk!
    if (_name.length() == 0) throw new ValueException("bad torrent name");

    _incompleteFile =
        new File(
            SharingSettings.INCOMPLETE_DIRECTORY.get(),
            Base32.encode(infoHash) + File.separator + _name);
    _completeFile = new File(SharingSettings.getSaveDirectory(_name), _name);

    if (!FileUtils.isReallyParent(SharingSettings.getSaveDirectory(_name), _completeFile)) {
      throw new SaveLocationException(LocationCode.SECURITY_VIOLATION, _completeFile);
    }

    if (data.getFiles() != null) {
      List<BTData.BTFileData> files = data.getFiles();
      List<TorrentFile> torrents = new ArrayList<TorrentFile>(files.size());
      long position = 0;
      for (BTData.BTFileData file : files) {
        String torrentPath = _name + file.getPath();
        TorrentFile f =
            new TorrentFile(
                file.getLength(),
                new File(_completeFile, file.getPath()).getAbsolutePath(),
                torrentPath);
        f.setBeginPiece((int) (position / pieceLength));
        f.setStartByte(position);
        position += f.length();
        f.setEndPiece((int) (position / pieceLength));
        f.setEndByte(position - 1);

        if (!FileUtils.isReallyInParentPath(_completeFile, f))
          throw new SaveLocationException(LocationCode.SECURITY_VIOLATION, f);
        torrents.add(f);
      }

      if (files.size() == 0) throw new ValueException("bad metainfo, no files!");

      _files = torrents;

      // add folders, for easier conflict checking later on
      for (String folderPath : data.getFolders()) _folders.add(new File(_completeFile, folderPath));
      _folders.add(_completeFile);
    } else {
      String torrentPath = data.getName();
      TorrentFile f =
          new TorrentFile(data.getLength(), _completeFile.getAbsolutePath(), torrentPath);
      f.setBeginPiece(0);
      f.setStartByte(0);
      f.setEndPiece(numHashes - 1);
      f.setEndByte(f.length());
      _files = new ArrayList<TorrentFile>(1);
      _files.add(f);
    }

    _unmodFiles = Collections.unmodifiableList(_files);
    _totalSize = calculateTotalSize(_files);
    if (_totalSize <= 0) throw new ValueException("invalid size " + _totalSize);
  }
  /**
   * Returns the fully-qualified temporary download file for the given file/location pair. If an
   * incomplete file already exists for this URN, that file is returned. Otherwise, the location of
   * the file is determined by the "incDir" variable. For example, getFile("test.txt", 1999) may
   * return "C:\Program Files\LimeWire\Incomplete\T-1999-Test.txt" if "incDir" is "C:\Program
   * Files\LimeWire\Incomplete". The disk is not modified, except for the file possibly being
   * created.
   *
   * <p>This method gives duplicate files the same temporary file, which is critical for resume and
   * swarmed downloads. That is, for all rfd_i and rfd_j
   *
   * <pre>
   *      similar(rfd_i, rfd_j) <==> getFile(rfd_i).equals(getFile(rfd_j))<p>
   * </pre>
   *
   * It is imperative that the files are compared as in their canonical formats to preserve the
   * integrity of the filesystem. Otherwise, multiple downloads could be downloading to "FILE A",
   * and "file a", although only "file a" exists on disk and is being written to by both.
   *
   * @throws IOException if there was an IOError while determining the file's name.
   */
  public synchronized File getFile(String name, URN sha1, long size, File incDir)
      throws IOException {
    boolean dirsMade = false;
    File baseFile = null;
    File canonFile = null;

    // make sure its created.. (the user might have deleted it)
    dirsMade = incDir.mkdirs();

    String convertedName = CommonUtils.convertFileName(name);

    try {

      if (sha1 != null) {
        File file = hashes.get(sha1);
        if (file != null) {
          // File already allocated for hash
          return file;
        } else {
          // Allocate unique file for hash.  By "unique" we mean not in
          // the value set of HASHES.  Because we allow risky resumes,
          // there's no need to look at BLOCKS as well...
          for (int i = 1; ; i++) {
            file = new File(incDir, tempName(convertedName, size, i));
            baseFile = file;
            file = canonicalize(file);
            canonFile = file;
            if (!hashes.values().contains(file)) break;
          }
          // ...and record the hash for later.
          hashes.put(sha1, file);
          // ...and make sure the file exists on disk, so that
          //   future File.getCanonicalFile calls will match this
          //   file.  This was a problem on OSX, where
          //   File("myfile") and File("MYFILE") aren't equal,
          //   but File("myfile").getCanonicalFile() will only return
          //   a File("MYFILE") if that already existed on disk.
          //   This means that in order for the canonical-checking
          //   within this class to work, the file must exist on disk.
          FileUtils.touch(file);

          return file;
        }
      } else {
        // No hash.
        File f = new File(incDir, tempName(convertedName, size, 0));
        baseFile = f;
        f = canonicalize(f);
        canonFile = f;
        return f;
      }

    } catch (IOException ioe) {
      IOException ioe2 =
          new IOException(
              "dirsMade: "
                  + dirsMade
                  + "\ndirExist: "
                  + incDir.exists()
                  + "\nbaseFile: "
                  + baseFile
                  + "\ncannFile: "
                  + canonFile);
      ioe2.initCause(ioe);
      throw ioe2;
    }
  }