Files
ncdownloader/lib/Tools/Aria2.php
2021-09-10 22:09:41 +08:00

373 lines
11 KiB
PHP

<?php
namespace OCA\NCDownloader\Tools;
//use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
class Aria2
{
//extra Aria2 download options
private $options = array();
//optional token for authenticating with Aria2
private $token = null;
//the aria2 method being invoked
private $method = null;
//the aria2c binary path
private $bin = null;
// return the following items when getting downloads info by default
private $dataFilter = array(
'status', 'followedBy', 'totalLength', 'errorMessage', 'dir', 'uploadLength', 'completedLength', 'downloadSpeed', 'files', 'numSeeders', 'connections', 'gid', 'following', 'bittorrent',
);
//whether to filter the response returned by aria2
private $filterResponse = true;
//absolute download path
private $downloadDir;
public function __construct($options = array())
{
$options += array(
'host' => '127.0.0.1',
'port' => 6800,
'dir' => '/tmp/Downloads',
'token' => null,
'conf_dir' => '/tmp/aria2',
'settings' => [],
);
//turn keys in $options into variables
extract($options);
$this->setDownloadDir($dir);
if (!empty($settings)) {
foreach ($settings as $key => $value) {
$this->setOption($key, $value);
}
}
$this->bin = Helper::findBinaryPath('aria2c');
$this->rpcUrl = sprintf("http://%s:%s/jsonrpc", $host, $port);
$this->tokenString = $token ?? 'ncdownloader123';
$this->setToken($this->tokenString);
$this->confDir = $conf_dir;
$this->sessionFile = $this->confDir . "/aria2.session";
$this->confFile = $this->confDir . "/aria2.conf";
$this->logFile = $this->confDir . "/aria2.log";
}
public function init()
{
$this->ch = curl_init($this->rpcUrl);
curl_setopt_array($this->ch, array(
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_SSL_VERIFYPEER => false,
));
$this->configure();
}
public function setonDownloadStart($path)
{
$this->onDownloadStart = $path;
}
public function reset()
{
$this->init();
}
private function hasOption($key)
{
return (bool) isset($this->options[$key]);
}
private function configure()
{
if (!is_dir($this->confDir)) {
mkdir($this->confDir, 0755, true);
}
if (!file_exists($this->confDir . "/aria2.conf")) {
file_put_contents($this->confDir . "/aria2.conf", $this->confTemplate());
}
if (!is_dir($dir = $this->getDownloadDir())) {
mkdir($dir, 0755, true);
}
$this->followTorrent(true);
}
public function setToken($token)
{
$this->token = "token:$token";
return $this;
}
public function setOption($key, $value)
{
$this->options[$key] = $value;
return $this;
}
public function getOptions()
{
return $this->options;
}
public function setDownloadDir($dir)
{
$this->setOption('dir', $dir);
$this->downloadDir = $dir;
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
return $this;
}
public function getDownloadDir()
{
return $this->downloadDir;
}
public function setFileName($file)
{
$this->options['out'] = $file;
return $this;
}
public function followTorrent($follow)
{
$this->options['follow-torrent'] = $follow;
return $this;
}
private function request($data)
{
$this->init();
$defaults = array(
'jsonrpc' => '2.0',
'id' => 'ncdownloader',
'method' => 'aria2.addUri',
'params' => null,
);
$data += $defaults;
$this->content = json_encode($data);
if (isset($this->content)) {
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->content);
}
$resp = curl_exec($this->ch);
curl_close($this->ch);
return json_decode($resp, 1);
}
public function getFollowingGid($gid)
{
$data = $this->tellStatus($gid);
if (!is_array($data)) {
return 0;
}
$data = reset($data);
return ($data['following'] ?? 0);
}
public function tellFail($range = [0, 999])
{
$this->filterResponse = false;
$resp = $this->tellStopped($range);
$result = $this->sortDownloadsResult($resp['result'], ['complete', 'removed']);
$this->filterResponse = true;;
return $result;
}
public function getCounters()
{
$active = is_array($data = $this->tellActive([])) ? count($data) : 0;
$waiting = is_array($data = $this->tellWaiting([0, 999])) ? count($data) : 0;
$stopped = is_array($data = $this->tellStopped([0, 999])) ? count($data) : 0;
$fail = is_array($data = $this->tellFail([0, 999])) ? count($data) : 0;
return ['active' => $active, 'waiting' => $waiting, 'complete' => $stopped, 'fail' => $fail];
}
public function tellAll()
{
$this->filterResponse = false;
return array_merge($this->tellActive([]), $this->tellWaiting([0, 999]), $this->tellStopped([0, 999]));
}
public function __call($name, $args)
{
$this->methodName = $name;
$data = array();
if (isset($args[0]) && is_array($args[0]) && count($args) == 1 && strtolower($name) !== "adduri") {
$args = reset($args);
}
switch ($name) {
case "addUri":
array_push($args, $this->options);
break;
case "tellActive":
case "tellWaiting":
case "tellStopped":
array_push($args, $this->dataFilter);
break;
case "tellStatus":
case "getFiles":
array_push($args, $this->dataFilter);
break;
}
if (isset($this->token)) {
array_unshift($args, $this->token);
}
$data = array('params' => $args, 'method' => 'aria2.' . $name);
$rawResp = $this->request($data);
if (!$this->filterResponse) {
return $rawResp;
}
return $this->parseResp($rawResp);
}
private function sortDownloadsResult($result, $statusFilter = null)
{
$data = [];
if (!isset($statusFilter)) {
$statusFilter = ['error'];
}
if (empty($result)) {
return [];
}
foreach ($result as $info) {
$info = Helper::filterData($info);
if (isset($info['files'])) {
foreach ($info['files'] as $key => &$files) {
$files = Helper::filterData($files, array('path', 'length'));
}
}
if (in_array($info['status'], $statusFilter)) {
continue;
}
array_push($data, $info);
}
return $data;
}
public function parseResp($resp = array())
{
$data = array();
if (isset($resp['error']) && isset($resp['error']['message'])) {
$data['error'] = $resp['error']['message'];
return $data;
}
$result = $resp['result'] ?? null;
if (!isset($result)) {
return $data;
}
if ($this->methodName === 'tellStatus' && isset($result['files'])) {
foreach ($result['files'] as $key => &$files) {
$files = Helper::filterData($files, array('path', 'length'));
}
array_push($data, $result);
return $data;
}
// parse response for tellActive,tellWaiting,and tellStopped
if (strpos($this->methodName, "tell") !== false && is_array($result)) {
return $this->sortDownloadsResult($result);
}
return $resp;
}
public function getStatus($gid)
{
return $this->tellStatus($gid);
}
public function restart()
{
$this->stop();
$this->start();
}
public function getDefaults()
{
return [
'--continue',
'--daemon=true',
'--enable-rpc=true',
'--rpc-secret=' . $this->tokenString,
'--listen-port=51413',
'--save-session=' . $this->sessionFile,
'--input-file=' . $this->sessionFile,
'--log=' . $this->logFile,
'--rpc-listen-port=6800',
'--follow-torrent=true',
'--enable-dht=true',
'--enable-peer-exchange=true',
'--peer-id-prefix=-TR2770-',
'--user-agent=Transmission/2.77',
'--log-level=info',
'--seed-ratio=1.0',
'--bt-seed-unverified=true',
'--max-overall-upload-limit=1M',
'--max-overall-download-limit=0',
'--max-connection-per-server=4',
'--max-concurrent-downloads=5',
];
}
public function start($bin = null)
{
//aria2c wont't start without any errors when input-file is set but missing
if (!file_exists($this->sessionFile)) {
file_put_contents($this->sessionFile, '');
}
//$process = new Process([$this->bin, "--conf-path=" . $this->confFile]);
$defaults = $this->getDefaults();
array_unshift($defaults, $this->bin);
$process = new Process($defaults);
try {
$process->mustRun();
$output = $process->getOutput();
} catch (ProcessFailedException $exception) {
$error = $exception->getMessage();
}
$resp = [];
if (isset($error)) {
$resp['error'] = $error;
$resp['status'] = false;
} else {
$resp['status'] = true;
}
return $resp;
}
public function isInstalled()
{
return (bool) isset($this->bin);
}
public function isRunning()
{
$resp = $this->getSessionInfo();
return (bool) $resp;
}
public function stop()
{
$resp = $this->shutdown();
return $resp ?? null;
}
private function confTemplate()
{
return <<<EOF
continue
daemon=true
#dir=/home/aria2/Downloads
#file-allocation=falloc
log-level=info
max-connection-per-server=4
max-concurrent-downloads=5
max-overall-download-limit=0
min-split-size=5M
enable-http-pipelining=true
#interface=127.0.0.1
enable-rpc=true
rpc-secret=$this->tokenString
rpc-listen-all=true
rpc-listen-port=6800
follow-torrent=true
listen-port=51413
enable-dht=true
enable-peer-exchange=true
peer-id-prefix=-TR2770-
user-agent=Transmission/2.77
seed-ratio=0.1
bt-seed-unverified=true
max-overall-upload-limit=1M
#on-download-complete=$this->onDownloadComplete
#on-download-error=$this->onDownloadError
#on-download-start=$this->onDownloadStart
save-session=$this->sessionFile
input-file=$this->sessionFile
log=$this->logFile
EOF;
}
}