/* * File : TOTorrentCreateImpl.java * Created : 5 Oct. 2003 * By : Parg * * Azureus - a Java Bittorrent client * * 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. * * 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 ( see the LICENSE file ). * * 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 com.frostwire.torrent; import java.io.ByteArrayOutputStream; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.GZIPOutputStream; final class TOTorrentCreateImpl extends TOTorrentImpl implements TOTorrentFileHasherListener { private File torrent_base; private long piece_length; private TOTorrentFileHasher file_hasher; private long total_file_size = -1; private long total_file_count = 0; private long piece_count; private boolean add_other_hashes; private List<TOTorrentProgressListener> progress_listeners = new ArrayList<TOTorrentProgressListener>(); private int reported_progress; private Set<String> ignore_set = new HashSet<String>(); private Map<String, File> linkage_map; private Map<String, Object> linked_tf_map = new HashMap<String, Object>(); private boolean cancelled; protected TOTorrentCreateImpl(Map<String, File> _linkage_map, File _torrent_base, URI _announce_url, boolean _add_other_hashes, long _piece_length) throws TOTorrentException { super(_torrent_base.getName(), _announce_url, _torrent_base.isFile()); linkage_map = _linkage_map; torrent_base = _torrent_base; piece_length = _piece_length; add_other_hashes = _add_other_hashes; } protected TOTorrentCreateImpl(Map<String, File> _linkage_map, File _torrent_base, URI _announce_url, boolean _add_other_hashes, long _piece_min_size, long _piece_max_size, long _piece_num_lower, long _piece_num_upper) throws TOTorrentException { super(_torrent_base.getName(), _announce_url, _torrent_base.isFile()); linkage_map = _linkage_map; torrent_base = _torrent_base; add_other_hashes = _add_other_hashes; long total_size = calculateTotalFileSize(_torrent_base); piece_length = getComputedPieceSize(total_size, _piece_min_size, _piece_max_size, _piece_num_lower, _piece_num_upper); } protected void create() throws TOTorrentException { constructFixed(torrent_base, piece_length); if (linkage_map.size() != linked_tf_map.size()) { throw (new TOTorrentException("TOTorrentCreate: unresolved linkages: required=" + linkage_map + ", resolved=" + linked_tf_map, TOTorrentException.RT_DECODE_FAILS)); } if (linked_tf_map.size() > 0) { Map<String, Object> m = getAdditionalMapProperty(TOTorrent.AZUREUS_PRIVATE_PROPERTIES); if (m == null) { m = new HashMap<String, Object>(); setAdditionalMapProperty(TOTorrent.AZUREUS_PRIVATE_PROPERTIES, m); } if (linked_tf_map.size() < 100) { m.put(TorrentUtils.TORRENT_AZ_PROP_INITIAL_LINKAGE, linked_tf_map); } else { ByteArrayOutputStream baos = new ByteArrayOutputStream(100 * 1024); try { GZIPOutputStream gos = new GZIPOutputStream(baos); gos.write(BEncoder.encode(linked_tf_map)); gos.close(); m.put(TorrentUtils.TORRENT_AZ_PROP_INITIAL_LINKAGE2, baos.toByteArray()); } catch (Throwable e) { throw (new TOTorrentException("Failed to serialise linkage", TOTorrentException.RT_WRITE_FAILS)); } } } } protected void constructFixed(File _torrent_base, long _piece_length) throws TOTorrentException { setIgnoreList(); setCreationDate(System.currentTimeMillis() / 1000); setCreatedBy(Constants.APP_NAME + "/" + Constants.APP_VERSION); setPieceLength(_piece_length); report("Torrent.create.progress.piecelength", _piece_length); piece_count = calculateNumberOfPieces(_torrent_base, _piece_length); if (piece_count == 0) { throw (new TOTorrentException("TOTorrentCreate: specified files have zero total length", TOTorrentException.RT_ZERO_LENGTH)); } report("Torrent.create.progress.hashing"); for (int i = 0; i < progress_listeners.size(); i++) { ((TOTorrentProgressListener) progress_listeners.get(i)).reportProgress(0); } boolean add_other_per_file_hashes = add_other_hashes && !getSimpleTorrent(); file_hasher = new TOTorrentFileHasher(add_other_hashes, add_other_per_file_hashes, (int) _piece_length, progress_listeners.size() == 0 ? null : this); try { if (cancelled) { throw (new TOTorrentException("TOTorrentCreate: operation cancelled", TOTorrentException.RT_CANCELLED)); } if (getSimpleTorrent()) { File link = linkage_map.get(_torrent_base.getName()); if (link != null) { linked_tf_map.put("0", link.getAbsolutePath()); } long length = file_hasher.add(link == null ? _torrent_base : link); setFiles(new TOTorrentFileImpl[] { new TOTorrentFileImpl(this, 0, length, new byte[][] { getName() }) }); setPieces(file_hasher.getPieces()); } else { List<TOTorrentFileImpl> encoded = new ArrayList<TOTorrentFileImpl>(); processDir(file_hasher, _torrent_base, encoded, _torrent_base.getName(), ""); TOTorrentFileImpl[] files = new TOTorrentFileImpl[encoded.size()]; encoded.toArray(files); setFiles(files); } setPieces(file_hasher.getPieces()); if (add_other_hashes) { byte[] sha1_digest = file_hasher.getSHA1Digest(); //byte[] ed2k_digest = file_hasher.getED2KDigest(); addAdditionalInfoProperty("sha1", sha1_digest); //addAdditionalInfoProperty( "ed2k", ed2k_digest ); //System.out.println( "overall:sha1 = " + ByteFormatter.nicePrint( sha1_digest, true)); //System.out.println( "overall:ed2k = " + ByteFormatter.nicePrint( ed2k_digest, true)); } } finally { file_hasher = null; } } protected void processDir(TOTorrentFileHasher hasher, File dir, List<TOTorrentFileImpl> encoded, String base_name, String root) throws TOTorrentException { File[] dir_file_list = dir.listFiles(); if (dir_file_list == null) { throw (new TOTorrentException("TOTorrentCreate: directory '" + dir.getAbsolutePath() + "' returned error when listing files in it", TOTorrentException.RT_FILE_NOT_FOUND)); } // sort contents so that multiple encodes of a dir always // generate same torrent List<File> file_list = new ArrayList<File>(Arrays.asList(dir_file_list)); Collections.sort(file_list); long offset = 0; for (int i = 0; i < file_list.size(); i++) { File file = (File) file_list.get(i); String file_name = file.getName(); if (!(file_name.equals(".") || file_name.equals(".."))) { if (file.isDirectory()) { if (root.length() > 0) { file_name = root + File.separator + file_name; } processDir(hasher, file, encoded, base_name, file_name); } else { if (!ignoreFile(file_name)) { if (root.length() > 0) { file_name = root + File.separator + file_name; } File link = linkage_map.get(base_name + File.separator + file_name); if (link != null) { linked_tf_map.put(String.valueOf(encoded.size()), link.getAbsolutePath()); } long length = hasher.add(link == null ? file : link); TOTorrentFileImpl tf = new TOTorrentFileImpl(this, offset, length, file_name); offset += length; if (add_other_hashes) { //byte[] ed2k_digest = hasher.getPerFileED2KDigest(); byte[] sha1_digest = hasher.getPerFileSHA1Digest(); //System.out.println( "file:ed2k = " + ByteFormatter.nicePrint( ed2k_digest, true )); //System.out.println( "file:sha1 = " + ByteFormatter.nicePrint( sha1_digest, true )); tf.setAdditionalProperty("sha1", sha1_digest); //tf.setAdditionalProperty( "ed2k", ed2k_digest ); } encoded.add(tf); } } } } } public void pieceHashed(int piece_number) { for (int i = 0; i < progress_listeners.size(); i++) { int this_progress = (int) ((piece_number * 100) / piece_count); if (this_progress != reported_progress) { reported_progress = this_progress; ((TOTorrentProgressListener) progress_listeners.get(i)).reportProgress(reported_progress); } } } protected long calculateNumberOfPieces(File _file, long _piece_length) throws TOTorrentException { long res = getPieceCount(calculateTotalFileSize(_file), _piece_length); report("Torrent.create.progress.piececount", "" + res); return (res); } protected long calculateTotalFileSize(File file) throws TOTorrentException { if (total_file_size == -1) { total_file_size = getTotalFileSize(file); } return (total_file_size); } protected long getTotalFileSize(File file) throws TOTorrentException { report("Torrent.create.progress.parsingfiles"); long res = getTotalFileSizeSupport(file, ""); report("Torrent.create.progress.totalfilesize", res); report("Torrent.create.progress.totalfilecount", "" + total_file_count); return (res); } protected long getTotalFileSizeSupport(File file, String root) throws TOTorrentException { String name = file.getName(); if (name.equals(".") || name.equals("..")) { return (0); } if (!file.exists()) { throw (new TOTorrentException("TOTorrentCreate: file '" + file.getName() + "' doesn't exist", TOTorrentException.RT_FILE_NOT_FOUND)); } if (file.isFile()) { if (!ignoreFile(name)) { total_file_count++; if (root.length() > 0) { name = root + File.separator + name; } File link = linkage_map.get(name); return (link == null ? file.length() : link.length()); } else { return (0); } } else { File[] dir_files = file.listFiles(); if (dir_files == null) { throw (new TOTorrentException("TOTorrentCreate: directory '" + file.getAbsolutePath() + "' returned error when listing files in it", TOTorrentException.RT_FILE_NOT_FOUND)); } long length = 0; if (root.length() == 0) { root = name; } else { root = root + File.separator + name; } for (int i = 0; i < dir_files.length; i++) { length += getTotalFileSizeSupport(dir_files[i], root); } return (length); } } protected void report(String resource_key) { report(resource_key, null); } protected void report(String resource_key, long bytes) { if (progress_listeners.size() > 0) { report(resource_key, String.valueOf(bytes)); } } protected void report(String resource_key, String additional_text) { if (progress_listeners.size() > 0) { String prefix = resource_key; for (int i = 0; i < progress_listeners.size(); i++) { ((TOTorrentProgressListener) progress_listeners.get(i)).reportCurrentTask(prefix + (additional_text == null ? "" : additional_text)); } } } public static long getComputedPieceSize(long total_size, long _piece_min_size, long _piece_max_size, long _piece_num_lower, long _piece_num_upper) { long piece_length = -1; long current_piece_size = _piece_min_size; while (current_piece_size <= _piece_max_size) { long pieces = total_size / current_piece_size; if (pieces <= _piece_num_upper) { piece_length = current_piece_size; break; } current_piece_size = current_piece_size << 1; } // if we haven't set a piece length here then there are too many pieces even // at maximum piece size. Go for largest piece size if (piece_length == -1) { // just go for the maximum piece size piece_length = _piece_max_size; } return (piece_length); } public static long getPieceCount(long total_size, long piece_size) { return ((total_size + (piece_size - 1)) / piece_size); } protected void setIgnoreList() { try { ignore_set = Collections.emptySet();// TorrentUtils.getIgnoreSet(); } catch (NoClassDefFoundError e) { return; } } protected boolean ignoreFile(String file) { if (ignore_set.contains(file.toLowerCase())) { report("Torrent.create.progress.ignoringfile", " '" + file + "'"); return (true); } return (false); } protected void cancel() { if (!cancelled) { report("Torrent.create.progress.cancelled"); cancelled = true; if (file_hasher != null) { file_hasher.cancel(); } } } protected void addListener(TOTorrentProgressListener listener) { progress_listeners.add(listener); } protected void removeListener(TOTorrentProgressListener listener) { progress_listeners.remove(listener); } }