/*
* 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);
}
}