From f71ba139e2c52ca041c291109f6921eff0237d48 Mon Sep 17 00:00:00 2001 From: Ramon Kleiss Date: Sat, 13 Apr 2013 01:12:59 +0200 Subject: [PATCH] Changed the entire model organization The base model is Transmission\Torrent now which extends from Transmission\Model\Torrent. This way, all the static methods are neatly organized in the base torrent model (Transmission\Torrent) while the actual representation is done by the actual model (Transmission\Model\Torrent). This has the adventage that new fields are more easy to add since it doens't make the entire model less easy to read. --- lib/Transmission/Client.php | 4 +- lib/Transmission/Model/Torrent.php | 75 +++++ lib/Transmission/Torrent.php | 190 ++++++++++++ tests/Transmission/Tests/TorrentTest.php | 374 +++++++++++++++++++++++ tests/fixtures/add_torrent.json | 10 + tests/fixtures/get_all_torrents.json | 36 +++ tests/fixtures/get_torrent.json | 26 ++ tests/fixtures/get_torrent_status.json | 12 + tests/fixtures/remove_torrent.json | 4 + 9 files changed, 729 insertions(+), 2 deletions(-) create mode 100644 lib/Transmission/Model/Torrent.php create mode 100644 lib/Transmission/Torrent.php create mode 100644 tests/Transmission/Tests/TorrentTest.php create mode 100644 tests/fixtures/add_torrent.json create mode 100644 tests/fixtures/get_all_torrents.json create mode 100644 tests/fixtures/get_torrent.json create mode 100644 tests/fixtures/get_torrent_status.json create mode 100644 tests/fixtures/remove_torrent.json diff --git a/lib/Transmission/Client.php b/lib/Transmission/Client.php index 777a5ec..37543c7 100644 --- a/lib/Transmission/Client.php +++ b/lib/Transmission/Client.php @@ -16,7 +16,7 @@ class Client /** * @var string */ - const RPC_PATH = '/transmission/rpc'; + const DEFAULT_PATH = '/transmission/rpc'; /** * @var string @@ -217,7 +217,7 @@ protected function getUrl() 'http://%s:%d%s', $this->getHost(), $this->getPort(), - self::RPC_PATH + self::DEFAULT_PATH ); } diff --git a/lib/Transmission/Model/Torrent.php b/lib/Transmission/Model/Torrent.php new file mode 100644 index 0000000..51b885a --- /dev/null +++ b/lib/Transmission/Model/Torrent.php @@ -0,0 +1,75 @@ + + */ +class Torrent extends AbstractModel +{ + /** + * @var integer + */ + protected $id; + + /** + * @var string + */ + protected $name; + + /** + * Constructor + * + * @param Transmission\Client $client + */ + public function __construct(Client $client = null) + { + parent::__construct($client); + } + + /** + * @param integer $id + */ + public function setId($id) + { + $this->id = (integer) $id; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = (string) $name; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return array + */ + protected static function getMapping() + { + return array( + 'id' => 'id', + 'name' => 'name' + ); + } +} diff --git a/lib/Transmission/Torrent.php b/lib/Transmission/Torrent.php new file mode 100644 index 0000000..cdc2af2 --- /dev/null +++ b/lib/Transmission/Torrent.php @@ -0,0 +1,190 @@ + array(array_keys(self::getMapping())) + ); + + $response = $client->call('torrent-get', $arguments); + + self::validateResponse($response, 'all'); + + $torrents = array(); + + foreach ($response->arguments->torrents as $t) { + $torrents[] = self::transformTorrent($t, $client); + } + + return $torrents; + } + + /** + * Get a torrent from the download queue by id + * + * @param integer $id + * @param Transmission\Client $client + * @return Transmission\Torrent + */ + public static function get($id, Client $client = null) + { + $client = $client ?: new Client(); + $arguments = array( + 'fields' => array(array_keys(self::getMapping())), + 'ids' => array($id) + ); + + $response = $client->call('torrent-get', $arguments); + + self::validateResponse($response, 'get'); + + return self::transformTorrent( + $response->arguments->torrents[0], + $client + ); + } + + /** + * Add a torrent to the download queue + * + * @param string $torrent + * @param Transmission\Client $client + * @param boolean $meta + * @return Transmission\Torrent + */ + public static function add($torrent, Client $client = null, $meta = false) + { + $client = $client ?: new Client(); + $arguments = array(); + $property = 'torrent-added'; + + $arguments[$meta ? 'metainfo' : 'torrent'] = (string) $torrent; + + $response = $client->call('torrent-add', $arguments); + + self::validateResponse($response, 'add'); + + return self::transformTorrent( + $response->arguments->$property, $client + ); + } + + /** + * Remove a torrent from the download queue + * + * @param boolean $localData + */ + public function delete($localData = false) + { + $arguments = array( + 'ids' => array($this->getId()), + 'delete-local-data' => $localData + ); + + $response = $this->getClient()->call('torrent-remove', $arguments); + + self::validateResponse($response, 'delete'); + } + + /** + * @param stdClass $torrentData + * @param Transmission\Client $client + * @return Transmission\Torrent + */ + private static function transformTorrent(\stdClass $torrentData, Client $client) + { + return ResponseTransformer::transform( + $torrentData, + new Torrent($client), + self::getMapping() + ); + } + + /** + * @param stdClass $response + * @param string $method + */ + private static function validateResponse(\stdClass $response, $method = null) + { + if (!isset($response->result)) { + throw new InvalidResponseException( + 'Invalid response received from Transmission' + ); + } + + if ($response->result !== 'success') { + throw new \RuntimeException(sprintf( + 'An error occured: "%s"', $response->result + )); + } + + switch ($method) { + case 'get': + return self::validateGetResponse($response); + case 'all': + return self::validateAllResponse($response); + case 'add': + return self::validateAddResponse($response); + } + } + + /** + * @param stdClass $response + */ + private static function validateGetResponse(\stdClass $response) + { + if (!isset($response->arguments) || + !isset($response->arguments->torrents)) { + throw new InvalidResponseException( + 'Invalid response received from Transmission' + ); + } + + if (!count($response->arguments->torrents)) { + throw new NoSuchTorrentException('Torrent not found in queue'); + } + } + + /** + * @param stdClass $response + */ + private static function validateAllResponse(\stdClass $response) + { + if (!isset($response->arguments) || + !isset($response->arguments->torrents)) { + throw new InvalidResponseException( + 'Invalid response received from Transmission' + ); + } + } + + /** + * @param stdClass $response + */ + private static function validateAddResponse(\stdClass $response) + { + $torrentField = 'torrent-added'; + + if (!isset($response->arguments) || + !isset($response->arguments->$torrentField) || + empty($response->arguments->$torrentField)) { + throw new InvalidResponseException( + 'Invalid response received from Transmission' + ); + } + } +} diff --git a/tests/Transmission/Tests/TorrentTest.php b/tests/Transmission/Tests/TorrentTest.php new file mode 100644 index 0000000..44a0ded --- /dev/null +++ b/tests/Transmission/Tests/TorrentTest.php @@ -0,0 +1,374 @@ +getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->with('torrent-get') + ->will($this->returnValue($this->loadFixture('get_all_torrents'))); + + $torrents = Torrent::all($client); + + $this->assertInternalType('array', $torrents); + $this->assertCount(2, $torrents); + + $this->assertEquals(1, $torrents[0]->getId()); + $this->assertEquals('Example 1', $torrents[0]->getName()); + + $this->assertEquals(2, $torrents[1]->getId()); + $this->assertEquals('Example 2', $torrents[1]->getName()); + } + + /** + * @test + * @expectedException RuntimeException + */ + public function shouldThrowExceptionOnUnsuccesfullAllTorrentsResultMessage() + { + $response = (object) array( + 'result' => 'Something went wrong' + ); + + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + Torrent::all($client); + } + + /** + * @test + * @expectedException Transmission\Exception\InvalidResponseException + * @dataProvider invalidTorrentAllProvider + */ + public function shouldThrowExceptionOnMissingFieldsInAllTorrentsRequest($response) + { + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + Torrent::all($client); + } + + /** + * @test + */ + public function shouldGetTorrent() + { + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->with('torrent-get') + ->will($this->returnValue($this->loadFixture("get_torrent"))); + + $torrent = Torrent::get(1, $client); + + $this->assertInstanceOf('Transmission\Torrent', $torrent); + $this->assertEquals($client, $torrent->getClient()); + $this->assertEquals(1, $torrent->getId()); + $this->assertEquals('Example', $torrent->getName()); + } + + /** + * @test + * @expectedException RuntimeException + */ + public function shouldThrowExceptionOnUnsuccesfullGetTorrentResultMessage() + { + $response = (object) array( + 'result' => 'Some error message' + ); + + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + Torrent::get(1, $client); + } + + /** + * @test + * @expectedException Transmission\Exception\InvalidResponseException + * @dataProvider invalidTorrentGetResponseProvider + */ + public function shouldThrowExceptionOnMissingFieldsInGetTorrentRequest($response) + { + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + Torrent::get(1, $client); + } + + /** + * @test + * @expectedException Transmission\Exception\NoSuchTorrentException + */ + public function shouldThrowExceptionWhenTorrentIsNotFoundInGetTorrentRequest() + { + $response = (object) array( + 'result' => 'success', + 'arguments' => (object) array( + 'torrents' => array() + ) + ); + + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + Torrent::get(1, $client); + } + + /** + * @test + */ + public function shouldAddTorrentsByFilename() + { + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->with( + 'torrent-add', + array('torrent' => 'foo') + ) + ->will($this->returnValue($this->loadFixture('add_torrent'))); + + $torrent = Torrent::add('foo', $client); + + $this->assertInstanceOf('Transmission\Torrent', $torrent); + $this->assertEquals($client, $torrent->getClient()); + $this->assertEquals(1, $torrent->getId()); + $this->assertEquals('Some+Added+Torrent', $torrent->getName()); + } + + /** + * @test + */ + public function shouldAddTorrentsByMetaInfo() + { + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->with( + 'torrent-add', + array('metainfo' => sha1('foo')) + ) + ->will($this->returnValue($this->loadFixture('add_torrent'))); + + $torrent = Torrent::add(sha1('foo'), $client, true); + + $this->assertInstanceOf('Transmission\Torrent', $torrent); + $this->assertEquals($client, $torrent->getClient()); + $this->assertEquals(1, $torrent->getId()); + $this->assertEquals('Some+Added+Torrent', $torrent->getName()); + } + + /** + * @test + * @expectedException RuntimeException + */ + public function shouldThrowExceptionOnUnsuccesfullAddTorrentResultMessage() + { + $response = (object) array( + 'result' => 'Something went wrong' + ); + + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + Torrent::add('foo', $client); + } + + /** + * @test + * @expectedException Transmission\Exception\InvalidResponseException + * @dataProvider invalidTorrentAddResponseProvider + */ + public function shouldThrowExceptionOnMissingFieldsInAddTorrentRequest($response) + { + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + Torrent::add('foo', $client); + } + + /** + * @test + */ + public function shouldRemoveTorrents() + { + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->with( + 'torrent-remove', + array( + 'ids' => array(1), + 'delete-local-data' => false + ) + ) + ->will($this->returnValue($this->loadFixture('remove_torrent'))); + + $torrent = new Torrent($client); + $torrent->setId(1); + $torrent->delete(); + } + + /** + * @test + * @expectedException RuntimeException + */ + public function shouldThrowExceptionOnUnsuccesfullRemoveTorrentResultMessage() + { + $response = (object) array( + 'result' => 'Something went wrong' + ); + + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + $torrent = new Torrent($client); + $torrent->setId(1); + $torrent->delete(); + } + + /** + * @test + * @expectedException Transmission\Exception\InvalidResponseException + */ + public function shouldThrowExceptionOnMissingFieldsInRemoveTorrentRequest() + { + $response = (object) array(); + + $client = $this->getMock('Transmission\Client'); + $client + ->expects($this->once()) + ->method('call') + ->will($this->returnValue($response)); + + $torrent = new Torrent($client); + $torrent->setId(1); + $torrent->delete(); + } + + public function invalidTorrentAllProvider() + { + $responses = array(); + + $responses[] = array((object) array( + 'arguments' => (object) array( + 'torrents' => array( + (object) array( + 'id' => 1, + 'name' => 'Example' + ) + ) + ) + )); + + $responses[] = array((object) array( + 'result' => 'success' + )); + + $responses[] = array((object) array( + 'result' => 'success', + 'arguments' => (object) array() + )); + + return $responses; + } + + public function invalidTorrentGetResponseProvider() + { + $responses = array(); + + $responses[] = array((object) array( + 'arguments' => (object) array( + 'torrents' => array( + (object) array( + 'id' => 1, + 'name' => 'Example' + ) + ) + ) + )); + + $responses[] = array((object) array( + 'result' => 'success' + )); + + $responses[] = array((object) array( + 'result' => 'success', + 'arguments' => (object) array() + )); + + return $responses; + } + + public function invalidTorrentAddResponseProvider() + { + $responses = array(); + + $responses[] = array((object) array( + 'arguments' => (object) array( + 'torrent-added' => array() + ) + )); + + $responses[] = array((object) array( + 'result' => 'success' + )); + + $responses[] = array((object) array( + 'result' => 'success', + 'arguments' => (object) array() + )); + + return $responses; + } + + protected function loadFixture($fixture) + { + $path = __DIR__."/../../fixtures/". $fixture .".json"; + + if (file_exists($path)) { + return json_decode(file_get_contents($path)); + } + + return (object) array(); + } +} diff --git a/tests/fixtures/add_torrent.json b/tests/fixtures/add_torrent.json new file mode 100644 index 0000000..9643f5c --- /dev/null +++ b/tests/fixtures/add_torrent.json @@ -0,0 +1,10 @@ +{ + "result": "success", + "arguments": { + "torrent-added": { + "id": 1, + "name": "Some+Added+Torrent", + "hashString": "loremipsumdolorsitamet" + } + } +} diff --git a/tests/fixtures/get_all_torrents.json b/tests/fixtures/get_all_torrents.json new file mode 100644 index 0000000..f82a710 --- /dev/null +++ b/tests/fixtures/get_all_torrents.json @@ -0,0 +1,36 @@ +{ + "result": "success", + "arguments": { + "torrents": [ + { + "id": 1, + "name": "Example 1", + "sizeWhenDone": 200, + "trackers": [ + { + "id": 1, + "tier": 1, + "scrape": "foo", + "announce": "bar" + } + ], + "files": [ + { + "name": "foo", + "length": 100, + "bytesCompleted": 10 + }, + { + "name": "bar", + "length": 100, + "bytesCompleted": 100 + } + ] + }, + { + "id": 2, + "name": "Example 2" + } + ] + } +} diff --git a/tests/fixtures/get_torrent.json b/tests/fixtures/get_torrent.json new file mode 100644 index 0000000..9cc7abf --- /dev/null +++ b/tests/fixtures/get_torrent.json @@ -0,0 +1,26 @@ +{ + "result": "success", + "arguments": { + "torrents": [ + { + "id": 1, + "name": "Example", + "trackers": [ + { + "id": 1, + "tier": 1, + "scrape": "foo", + "announce": "bar" + } + ], + "files": [ + { + "name": "foo", + "length": 1000, + "bytesCompleted": 0 + } + ] + } + ] + } +} diff --git a/tests/fixtures/get_torrent_status.json b/tests/fixtures/get_torrent_status.json new file mode 100644 index 0000000..5ff09bf --- /dev/null +++ b/tests/fixtures/get_torrent_status.json @@ -0,0 +1,12 @@ +{ + "result": "success", + "arguments": { + "torrents": [ + { "id": 1, "status": 0 }, + { "id": 2, "status": 3 }, + { "id": 3, "status": 4 }, + { "id": 4, "status": 2 }, + { "id": 5, "status": 6 } + ] + } +} diff --git a/tests/fixtures/remove_torrent.json b/tests/fixtures/remove_torrent.json new file mode 100644 index 0000000..716b20a --- /dev/null +++ b/tests/fixtures/remove_torrent.json @@ -0,0 +1,4 @@ +{ + "result": "success", + "arguments": {} +}