<?php
namespace TorrentPHP;
/**
* Interface ClientAdaptor
*
* If you want to add support for your own torrent client of choice, you need to create an ClientAdaptor that sends
* commands to your client. For transmission and vuze, these are RPC calls over the HTTP protocol, but your client may
* require command-line calls, for example.
*
* @package TorrentPHP
*/
interface ClientAdaptor
{
/**
* Add a torrent to the client
*
* Each client may have different requirements to add a torrent, so an array of arguments with the key being the
* argument name and the value being the argument value is required. It is therefore the specific implementation's
* responsibility to provide validation throughout it's usage.
*
* @param array $arguments A single dimensional array of key value pairs
*
* @throws ClientException When the client does not return expected output to say that this action succeeded
*
* @return Torrent The newly added torrent object
*/
public function addTorrent(array $arguments);
/**
* Start a torrent
*
* @param Torrent $torrent
*
* @throws ClientException When the client does not return expected output to say that this action succeeded
*
* @return Torrent The newly started torrent object
*/
public function startTorrent(Torrent $torrent);
/**
* Pause a torrent
*
* @param Torrent $torrent
*
* @throws ClientException When the client does not return expected output to say that this action succeeded
*
* @return Torrent The newly paused torrent object
*/
public function pauseTorrent(Torrent $torrent);
/**
* Delete a torrent - be aware this relates to deleting the torrent file and all files associated with it
*
* @param Torrent $torrent
*
* @throws ClientException When the client does not return expected output to say that this action succeeded
*
* @return void
*/
public function deleteTorrent(Torrent $torrent);
}
/**
* Class Torrent
*
* Represents the final torrent object provided by TorrentPHP
*
* @package TorrentPHP
*/
class Torrent
{
/**
* Default status
*/
const STATUS_UNKNOWN = -1;
/**
* A torrent client error has occurred
*/
const STATUS_ERROR = 0;
/**
* Torrent is downloading
*/
const STATUS_DOWNLOADING = 1;
/**
* Torrent is paused
*/
const STATUS_PAUSED = 2;
/**
* Torrent is seeding
*/
const STATUS_SEEDING = 3;
/**
* Torrent has completed both downloading and seeding
*/
const STATUS_COMPLETE = 4;
/**
* @var array An array of status matching the status constants for validation
*/
private $statuses = array(
self::STATUS_UNKNOWN => 'unknown',
self::STATUS_ERROR => 'error',
self::STATUS_DOWNLOADING => 'downloading',
self::STATUS_PAUSED => 'paused',
self::STATUS_SEEDING => 'seeding',
self::STATUS_COMPLETE => 'complete'
);
/**
* @var int The torrent id (used by the torrent client, and may change on each reboot)
*/
private $id;
/**
* @var string The torrent hashString which can be used to uniquely identify this torrent
*/
private $hashString = '';
/**
* @var string The torrent name
*/
private $name = '';
/**
* @var int Size of the torrent in bytes
*/
private $size = 0;
/**
* @var int Torrent status must be in the $statuses array
*/
private $status = self::STATUS_UNKNOWN;
/**
* @var string A string containing an error message
*/
private $errorString = '';
/**
* @var int Download speed in bytes
*/
private $downloadSpeed = 0;
/**
* @var int Upload speed in bytes
*/
private $uploadSpeed = 0;
/**
* @var float Percentage download completion
*/
private $percentDone = 0.0;
/**
* @var int Number of bytes downloaded
*/
private $bytesDownloaded = 0;
/**
* @var int Number of bytes uploaded
*/
private $bytesUploaded = 0;
/**
* @var float Seeding ratio
*/
private $seedRatio = 0.0;
/**
* @var File[] A list of files within the torrent
*/
private $files = array();
/**
* @constructor
*
* @param int $id The id used by the torrent client (may change on each reboot)
* @param string $hashString Uniquely identifiable hash string (remains constant)
*/
public function __construct($id, $hashString)
{
$this->id = $id;
$this->hashString = $hashString;
}
/**
* Set the torrent name
*
* @param string $name The name of the torrent
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get torrent name
*
* @return string The torrent name
*/
public function getName()
{
return $this->name;
}
/**
* Set the torrent size in bytes
*
* @param int $bytes The size of the torrent
*
* @throws \InvalidArgumentException When an invalid size given
*/
public function setSize($bytes)
{
if (is_int($bytes) && $bytes > -1)
{
$this->size = $bytes;
}
else
{
throw new \InvalidArgumentException(
'Invalid torrent size provided. Size should be bigger than "-1" but "%s" given', $bytes
);
}
}
/**
* Get torrent size
*
* @return int The torrent size in bytes
*/
public function getSize()
{
return $this->size;
}
/**
* Set the torrent status according to a status within the $statuses array
*
* @param int $status The status integer (constant representing an integer)
*
* @throws \InvalidArgumentException When an invalid status integer given
*/
public function setStatus($status)
{
if (is_int($status) && isset($this->statuses[$status]))
{
$this->status = $status;
}
else
{
throw new \InvalidArgumentException(sprintf(
'Invalid torrent status provided. Status should be one of "%s", "%s" given.',
print_r($this->statuses, true), $status
));
}
}
/**
* Get the status integer value
*
* @return int
*/
public function getStatus()
{
return $this->status;
}
/**
* Get the status string
*
* @return string
*/
public function getStatusString()
{
return $this->statuses[$this->status];
}
/**
* Set error string
*
* @param string $errorString
*/
public function setErrorString($errorString)
{
$this->errorString = $errorString;
}
/**
* Get error string
*
* @return string
*/
public function getErrorString()
{
return $this->errorString;
}
/**
* Set download speed in bytes per second
*
* @param int $bytesPerSecond
*
* @throws \InvalidArgumentException When an integer is not provided as the parameter
*/
public function setDownloadSpeed($bytesPerSecond)
{
if (is_int($bytesPerSecond) && $bytesPerSecond > -1)
{
$this->downloadSpeed = $bytesPerSecond;
}
else
{
throw new \InvalidArgumentException(sprintf(
'Invalid torrent download speed provided. Download speed should be non-negative integer, "%s" given.',
$bytesPerSecond
));
}
}
/**
* Get download speed in bytes per second
*
* @return int
*/
public function getDownloadSpeed()
{
return $this->downloadSpeed;
}
/**
* Set upload speed in bytes per second
*
* @param int $bytesPerSecond
*
* @throws \InvalidArgumentException When an integer is not provided as the parameter
*/
public function setUploadSpeed($bytesPerSecond)
{
if (is_int($bytesPerSecond) && $bytesPerSecond > -1)
{
$this->uploadSpeed = $bytesPerSecond;
}
else
{
throw new \InvalidArgumentException(sprintf(
'Invalid torrent upload speed provided. Upload speed should be non-negative integer, "%s" given.',
$bytesPerSecond
));
}
}
/**
* Get download speed in bytes per second
*
* @return int
*/
public function getUploadSpeed()
{
return $this->uploadSpeed;
}
/**
* Set number of bytes downloaded
*
* @param int $numBytes
*
* @throws \InvalidArgumentException When an integer is not provided as the parameter
*
* @note Also sets the completion percentage and status to complete if 100%
*/
public function setBytesDownloaded($numBytes)
{
if (is_int($numBytes) && $numBytes > -1)
{
$this->bytesDownloaded = $numBytes;
$size = $this->size;
$percentDone = function() use ($size, $numBytes) {
$size = ($size === 0) ? 1 : $size;
$numBytes = ($size > $numBytes) ? $numBytes : 0;
if ($size === 0 && $numBytes === 0) {
return (float)0;
}
return (float)number_format(($numBytes / $size) * 100, 2, '.', '');
};
$this->percentDone = $percentDone();
if ($this->percentDone === 100 || $numBytes === $this->size)
{
$this->status = self::STATUS_COMPLETE;
}
}
else
{
throw new \InvalidArgumentException(sprintf(
'Invalid torrent bytes downloaded provided. Amount should be non-negative integer, "%s" given.',
$numBytes
));
}
}
/**
* Get number of bytes downloaded
*
* @return int
*/
public function getBytesDownloaded()
{
return $this->bytesDownloaded;
}
/**
* Set number of bytes uploaded
*
* @param int $numBytes
*
* @throws \InvalidArgumentException
*
* @note Also sets the seed ratio percentage
*/
public function setBytesUploaded($numBytes)
{
if (is_int($numBytes) && $numBytes > -1)
{
$this->bytesUploaded = $numBytes;
$downloaded = $this->bytesDownloaded;
$seedRatio = function() use ($downloaded, $numBytes) {
$downloaded = ($downloaded === 0) ? 1 : $downloaded;
$numBytes = ($downloaded > $numBytes) ? $numBytes : 0;
if ($downloaded === 0 && $numBytes === 0) {
return (float)0;
}
return (float)number_format(($numBytes / $downloaded) * 100, 2, '.', '');
};
$this->seedRatio = $seedRatio();
}
else
{
throw new \InvalidArgumentException(sprintf(
'Invalid torrent bytes uploaded provided. Amount should be non-negative integer, "%s" given.',
$numBytes
));
}
}
/**
* Get number of bytes uploaded
*
* @return int
*/
public function getBytesUploaded()
{
return $this->bytesUploaded;
}
/**
* Add a file to the torrent
*
* @param File $file
*/
public function addFile(File $file)
{
if (!in_array($file, $this->files))
{
$this->files[] = $file;
}
}
/**
* Get the files list
*
* @return File[]
*/
public function getFiles()
{
return $this->files;
}
/**
* Get a specific File by name
*
* @param string $name
*
* @throws FileNotFoundException When the file does not exist with the given name
*
* @return File The found file
*/
public function getFile($name)
{
$files = array_values(array_filter($this->files, function($file) use ($name) {
/** @var File $file */
return strtolower($file->getName()) === strtolower($name);
}));
if (!empty($files))
{
return $files[0];
}
else
{
throw new FileNotFoundException(sprintf(
'File with name: "%s" not found for torrent: "%s"', $name, $this->name
));
}
}
/**
* Get whether or not the torrent has completed both downloading and seeding
*
* @return bool
*/
public function isComplete()
{
return ($this->status === self::STATUS_COMPLETE);
}
}