<?php
require_once 'class.bdecode.php';
require_once 'class.bencode.php';
require_once 'class.torrentinfo.php';
require_once 'class.torrentfile.php';
require_once 'class.torrentpiece.php';
/**
* Main Class, Check hashes
*
* @author Charalampos Pournaris
*/
class HashCheck {
/**
* Torrent file path
*
* @var string
*/
private $torrent_file = '';
/**
* Target directory path
*
* @var string
*/
private $target_path = '';
/**
* Torrent metadata file
*
* @var BDecode
*/
private $torrent = null;
/**
* Torrent metadata information
*
* @var TorrentInfo
*/
private $torrent_info = null;
/**
* Total data missing from $target_path
*
* @var integer
*/
private $total_data_missing = 0;
// Hash check related
/**
* Pieces that need to be re-downloaded
*
* @var array
*/
private $pieces_needed = array();
/**
* Pieces index array
*
* Each key corresponds to a piece number
*
* @var array
*/
private $pieces_index = array();
/**
* Files that should be checked
*
* @var array
*/
private $filelist = array();
/**
* Last piece's extra bytes counted
*
* @var unknown_type
*/
private $extra_bytes = 0;
/**
* Object constructor. Initializes member data
*
* @param string $torrent_file
* @param string $target_path
*/
public function __construct($torrent_file, $target_path) {
$this->torrent_file = $torrent_file;
$this->target_path = $target_path;
$this->torrent = new BDecode ( $torrent_file );
if (! empty ( $this->torrent->result ['error'] ))
printConsole ( "'$torrent_file' ".$this->torrent->result ['error'], true );
$this->torrent_info = new TorrentInfo ( $this->torrent );
$this->filelist = $this->torrent_info->getFilePathList ($target_path);
}
/**
* Create the pieces list for each torrent file
*
*/
public function makePieces() {
$piece_size = $this->torrent_info->getPieceLength();
$previous_shared = null;
$pieceid = 0;
TorrentPiece::$piece_size = $piece_size;
foreach ($this->filelist as &$file) {
$pieces = array();
$file_offset = 0;
if (!is_null($previous_shared)) {
if ($previous_shared->getRemaining() > $file['size']) {
$remaining = $previous_shared->getRemaining();
$previous_shared = new TorrentPiece($previous_shared->getPieceId(), $file_offset, $file['size'], false, true);
$previous_shared->setRemaining( $remaining - $file['size']);
$this->pieces_index[$previous_shared->getPieceId()]['filenames'][] = $file['path'];
$file['pieces'] = array($previous_shared);
unset($pieces);
continue;
} else {
$new_partial = new TorrentPiece($previous_shared->getPieceId(), $file_offset, $previous_shared->getRemaining(), false, true);
$file_offset = $previous_shared->getRemaining();
$new_partial->setRemaining(0);
$this->pieces_index[$new_partial->getPieceId()]['filenames'][] = $file['path'];
$pieces[] = $new_partial;
$previous_shared = null;
unset($new_partial);
}
}
while($file_offset + $piece_size <= $file['size']) {
++$pieceid;
$p = new TorrentPiece($pieceid, $file_offset, $file_offset+$piece_size, true, false);
$pieces[] = $p;
$this->pieces_index[$p->getPieceId()] = array('piece'=>$p,'filenames'=>array($file['path']));
$file_offset += $piece_size;
}
if ($file_offset < $file['size']) {
++$pieceid;
$previous_shared = new TorrentPiece($pieceid, $file_offset, $file['size'], false, true);
$this->pieces_index[$previous_shared->getPieceId()] = array('piece'=>$previous_shared,'filenames'=>array($file['path']));
$pieces[] = $previous_shared;
} else {
$previous_shared = null;
}
$file['pieces'] = $pieces;
unset($pieces);
}
// Count the last piece as complete - looks ugly I know..
$fl_last_element = count($this->filelist)-1;
$lelem = $this->filelist[$fl_last_element]['pieces'][count($this->filelist[$fl_last_element]['pieces'])-1];
$this->extra_bytes = $lelem->getRemaining();
$lelem->setRemaining(0);
unset($lelem);
}
/**
* Read the files, make the hashes and do the check
*
* This method also prints results
*
*/
public function doCheck() {
$piece_size = $this->torrent_info->getPieceLength();
$torrent_file = null;
$partial_data = '';
TorrentFile::$piece_size = $piece_size;
$this->makePieces();
foreach ($this->filelist as $file_info) {
$torrent_file = new TorrentFile($file_info['path'], $file_info['size'], $file_info['pieces']);
if ($torrent_file->open()) {
$torrent_file->read($partial_data);
$torrent_file->close();
$partial_data = $torrent_file->getPartialData();
} else {
foreach ($file_info['pieces'] as $piece) {
if (!in_array($piece->getPieceId(), $this->pieces_needed)) {
$this->pieces_needed[] = $piece->getPieceId();
}
}
}
}
foreach (TorrentFile::$hashes as $piece_num=>$hash) {
if (!$this->torrent_info->equalPieceHash($piece_num, $hash)) {
if (!in_array($piece_num, $this->pieces_needed)) {
$this->pieces_needed[] = $piece_num;
}
}
}
sort($this->pieces_needed);
foreach ($this->pieces_needed as $pid) {
$this->total_data_missing += $this->pieces_index[$pid]['piece']->getPieceLength();
printConsole("Piece #$pid:");
foreach ($this->pieces_index[$pid]['filenames'] as $file) {
printConsole($file);
}
printConsole('');
}
$total_file_size = $this->torrent_info->calculateLength();
printConsole("File percent valid: ".round((( (($total_file_size + $this->extra_bytes) - (count($this->pieces_needed) * $piece_size)) / ($total_file_size))*100),2).'% ('.round((((count($this->pieces_needed) * $piece_size)/1024)/1024),2)." mb missing)" );
}
}
?>