All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bt.metainfo.MetadataService Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
package bt.metainfo;

import bt.BtException;
import bt.bencoding.BEParser;
import bt.bencoding.BEType;
import bt.bencoding.model.BEList;
import bt.bencoding.model.BEMap;
import bt.bencoding.model.BEObject;
import bt.bencoding.model.BEObjectModel;
import bt.bencoding.model.BEString;
import bt.bencoding.model.ValidationResult;
import bt.bencoding.model.YamlBEObjectModelLoader;
import bt.service.CryptoUtil;
import bt.tracker.AnnounceKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 *

Note that this class implements a service. * Hence, is not a part of the public API and is a subject to change.

*/ public class MetadataService implements IMetadataService { private static final Logger LOGGER = LoggerFactory.getLogger(MetadataService.class); private static final String ANNOUNCE_KEY = "announce"; private static final String ANNOUNCE_LIST_KEY = "announce-list"; private static final String INFOMAP_KEY = "info"; private static final String TORRENT_NAME_KEY = "name"; private static final String CHUNK_SIZE_KEY = "piece length"; private static final String CHUNK_HASHES_KEY = "pieces"; private static final String TORRENT_SIZE_KEY = "length"; private static final String FILES_KEY = "files"; private static final String FILE_SIZE_KEY = "length"; private static final String FILE_PATH_ELEMENTS_KEY = "path"; private static final String PRIVATE_KEY = "private"; private BEObjectModel torrentModel; private BEObjectModel infodictModel; private Charset defaultCharset; public MetadataService() { this.defaultCharset = Charset.forName("UTF-8"); try { try (InputStream in = MetadataService.class.getResourceAsStream("/metainfo.yml")) { this.torrentModel = new YamlBEObjectModelLoader().load(in); } try (InputStream in = MetadataService.class.getResourceAsStream("/infodict.yml")) { this.infodictModel = new YamlBEObjectModelLoader().load(in); } } catch (IOException e) { throw new BtException("Failed to create metadata service", e); } } @Override public Torrent fromUrl(URL url) { try (BEParser parser = new BEParser(url)) { return buildTorrent(parser); } } @Override public Torrent fromInputStream(InputStream in) { try (BEParser parser = new BEParser(in)) { return buildTorrent(parser); } } @Override public Torrent fromByteArray(byte[] bs) { try (BEParser parser = new BEParser(bs)) { return buildTorrent(parser); } } @SuppressWarnings({"rawtypes", "unchecked"}) private Torrent buildTorrent(BEParser parser) { if (parser.readType() != BEType.MAP) { throw new BtException("Invalid metainfo format -- expected a map, got: " + parser.readType().name().toLowerCase()); } BEMap metadata = parser.readMap(); ValidationResult validationResult = torrentModel.validate(metadata);; if (!validationResult.isSuccess()) { ValidationResult infodictValidationResult = infodictModel.validate(metadata); if (!infodictValidationResult.isSuccess()) { throw new BtException("Validation failed for torrent metainfo:\n1. Standard torrent model: " + Arrays.toString(validationResult.getMessages().toArray()) + "\n2. Standalone info dictionary model: " + Arrays.toString(infodictValidationResult.getMessages().toArray())); } } BEMap infoDictionary; TorrentSource source; Map> root = metadata.getValue(); if (root.containsKey(INFOMAP_KEY)) { // standard BEP-3 format infoDictionary = (BEMap) root.get(INFOMAP_KEY); source = new TorrentSource() { @Override public Optional getMetadata() { return Optional.of(metadata.getContent()); } @Override public byte[] getExchangedMetadata() { return infoDictionary.getContent(); } }; } else { // BEP-9 exchanged metadata (just the info dictionary) infoDictionary = metadata; source = new TorrentSource() { @Override public Optional getMetadata() { return Optional.empty(); } @Override public byte[] getExchangedMetadata() { return infoDictionary.getContent(); } }; } DefaultTorrent torrent = new DefaultTorrent(source); try { torrent.setTorrentId(TorrentId.fromBytes(CryptoUtil.getSha1Digest(infoDictionary.getContent()))); Map> infoMap = infoDictionary.getValue(); if (infoMap.get(TORRENT_NAME_KEY) != null) { byte[] name = (byte[]) infoMap.get(TORRENT_NAME_KEY).getValue(); torrent.setName(new String(name, defaultCharset)); } BigInteger chunkSize = (BigInteger) infoMap.get(CHUNK_SIZE_KEY).getValue(); torrent.setChunkSize(chunkSize.longValueExact()); byte[] chunkHashes = (byte[]) infoMap.get(CHUNK_HASHES_KEY).getValue(); torrent.setChunkHashes(chunkHashes); if (infoMap.get(TORRENT_SIZE_KEY) != null) { BigInteger torrentSize = (BigInteger) infoMap.get(TORRENT_SIZE_KEY).getValue(); torrent.setSize(torrentSize.longValueExact()); } else { List files = (List) infoMap.get(FILES_KEY).getValue(); List torrentFiles = new ArrayList<>(files.size() + 1); BigInteger torrentSize = BigInteger.ZERO; for (BEMap file : files) { Map> fileMap = file.getValue(); DefaultTorrentFile torrentFile = new DefaultTorrentFile(); BigInteger fileSize = (BigInteger) fileMap.get(FILE_SIZE_KEY).getValue(); torrentFile.setSize(fileSize.longValueExact()); torrentSize = torrentSize.add(fileSize); List pathElements = (List) fileMap.get(FILE_PATH_ELEMENTS_KEY).getValue(); torrentFile.setPathElements(pathElements.stream() .map(bytes -> bytes.getValue(defaultCharset)) .collect(Collectors.toList())); torrentFiles.add(torrentFile); } torrent.setFiles(torrentFiles); torrent.setSize(torrentSize.longValueExact()); } boolean isPrivate = false; if (infoMap.get(PRIVATE_KEY) != null) { if (BigInteger.ONE.equals(infoMap.get(PRIVATE_KEY).getValue())) { torrent.setPrivate(true); isPrivate = true; } } AnnounceKey announceKey = null; // TODO: support for private torrents with multiple trackers if (!isPrivate && root.containsKey(ANNOUNCE_LIST_KEY)) { List> trackerUrls; BEList announceList = (BEList) root.get(ANNOUNCE_LIST_KEY); List tierList = (List) announceList.getValue(); trackerUrls = new ArrayList<>(tierList.size() + 1); for (BEList tierElement : tierList) { List tierTackerUrls; List trackerUrlList = (List) tierElement.getValue(); tierTackerUrls = new ArrayList<>(trackerUrlList.size() + 1); for (BEString trackerUrlElement : trackerUrlList) { tierTackerUrls.add(trackerUrlElement.getValue(defaultCharset)); } trackerUrls.add(tierTackerUrls); } announceKey = new AnnounceKey(trackerUrls); } else if (root.containsKey(ANNOUNCE_KEY)) { byte[] trackerUrl = (byte[]) root.get(ANNOUNCE_KEY).getValue(); announceKey = new AnnounceKey(new String(trackerUrl, defaultCharset)); } if (announceKey != null) { torrent.setAnnounceKey(announceKey); } } catch (Exception e) { throw new BtException("Invalid metainfo format", e); } return torrent; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy