luci-app-rtorrent: drop outdated package

This commit is contained in:
CN_SZTL 2020-04-13 01:21:51 +08:00
parent fcec9abc69
commit 531f5cc283
No known key found for this signature in database
GPG Key ID: 6850B6345C862176
109 changed files with 0 additions and 2958 deletions

View File

@ -1,60 +0,0 @@
NAME = luci-app-rtorrent
VERSION = $(shell awk '/^Version:/ {print $$2}' control/control)
ARCH = $(shell awk '/^Architecture:/ {print $$2}' control/control)
IPK = $(NAME)_$(VERSION)_$(ARCH).ipk
.PHONY: control
all: packages-file
ipk: clean control data
mkdir -p ipk
echo "2.0" > ipk/debian-binary
cd ipk && tar czf $(IPK) control.tar.gz data.tar.gz debian-binary
rm -f ipk/*.tar.gz ipk/debian-binary
control:
mkdir -p ipk
rm -f ../ipk/control.tar.gz
cd control && tar czf ../ipk/control.tar.gz *
data:
mkdir -p ipk
cd src && tar czf ../ipk/data.tar.gz *
key:
mkdir -p key
usign -G -s key/sign_key -p key/sign_key.pub -c "$(NAME) key"
cd key && usign -F -p sign_key.pub | xargs cp sign_key.pub
packages-file: ipk
mkdir -p ipk
cp control/control ipk/Packages
echo "Filename: $(IPK)" >> ipk/Packages
du -b ipk/$(IPK) | awk '{print "Size:", $$1}' >> ipk/Packages
md5sum ipk/$(IPK) | awk '{print "MD5Sum:", $$1}' >> ipk/Packages
sha256sum ipk/$(IPK) | awk '{print "SHA256sum:", $$1}' >> ipk/Packages
usign -S -m ipk/Packages -s key/sign_key
gzip ipk/Packages
clean:
rm -fr ipk
test-deploy: test-remove
cp -a src/usr/* /usr
cp -a src/www/* /www
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache
test-remove:
find src/usr src/www -type f -o -type l | sed 's/^src//' | xargs rm -f
rm -fr /usr/lib/lua/luci/model/cbi/rtorrent
rm -fr /usr/lib/lua/luci/view/rtorrent
rm -fr /usr/lib/lua/xmlrpc
rm -fr /www/luci-static/resources/icons/filetypes
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache
test-reinstall:
opkg list-installed | grep -q $(NAME) \
&& opkg --force-reinstall install $(NAME) \
|| echo "$(NAME) not installed, skip reinstall"

View File

@ -1,116 +0,0 @@
# luci-app-rtorrent
rTorrent client for OpenWrt's LuCI web interface
## Features
- List all torrent downloads
- Add new torrent by url/magnet uri/file
- Stop/start/delete torrents
- Mark torrents with tags
- Set priority per file
- Enable/disable and add trackers to torrent
- Detailed peer listing
- Completely LuCI based interface
- OpenWrt device independent (written in lua)
- Opkg package manager support
- RSS feed downloader (automatically download torrents that match the specified criteria)
- Support for [Transdroid](https://www.transdroid.org/) ([Transdrone](https://play.google.com/store/apps/details?id=org.transdroid.lite))
## Screenshots
[luci-app-rtorrent 0.1.3](https://github.com/wolandmaster/luci-app-rtorrent/wiki/Screenshots)
## Install instructions
(for Openwrt 15.05.1 Chaos Calmer)
### Install rtorrent-rpc
```
opkg update
opkg install rtorrent-rpc
```
(Note: If you going to install rtorrent-rpc to an own [opkg destination](https://wiki.openwrt.org/doc/techref/opkg#installation_destinations) then you have to install libopenssl to the root destination before)
### Create rTorrent config file
#### Minimal _/root/.rtorrent.rc_ file:
```
directory = /path/to/downloads/
session = /path/to/session/
scgi_port = 127.0.0.1:5000
schedule = rss_downloader,300,300,"execute=/usr/lib/lua/rss_downloader.lua"
```
#### Sample _/root/.rtorrent.rc_ file:
http://pissedoffadmins.com/os/linux/sample-rtorrent-rc-file.html
#### Recommended kernel parameters to avoid low memory issues:
```
cat /etc/sysctl.conf
...
# handle rtorrent related low memory issues
vm.swappiness=95
vm.vfs_cache_pressure=200
vm.min_free_kbytes=4096
vm.overcommit_memory=2
vm.overcommit_ratio=60
```
### Create init.d script (optional)
#### Install screen
```
opkg install screen
```
#### Create _/etc/init.d/rtorrent_ script
```
#!/bin/sh /etc/rc.common
START=99
STOP=99
SCREEN=/usr/sbin/screen
PROG=/usr/bin/rtorrent
start() {
sleep 3
$SCREEN -dm -t rtorrent nice -19 $PROG
}
stop() {
killall rtorrent
}
```
#### Start rtorrent
```
chmod +x /etc/init.d/rtorrent
/etc/init.d/rtorrent enable
/etc/init.d/rtorrent start
```
### Install wget
(the wget in busybox does not support https)
```
opkg install ca-certificates
opkg install wget
ln -sf $(which wget-ssl) /usr/bin/wget
```
### Install luci-app-rtorrent
```
wget -nv https://github.com/wolandmaster/luci-app-rtorrent/releases/download/latest/e1a1ba8004c4220f -O /etc/opkg/keys/e1a1ba8004c4220f
echo 'src/gz luci_app_rtorrent https://github.com/wolandmaster/luci-app-rtorrent/releases/download/latest' >> /etc/opkg.conf
opkg update
opkg install luci-app-rtorrent
```
### Upgrade already installed version
```
opkg update
opkg upgrade luci-app-rtorrent
```
### References
<https://www.pcsuggest.com/openwrt-torrent-download-box-luci/>
<https://medium.com/openwrt-iot/lede-openwrt-setting-up-torrent-downloading-a06fe37a1ea2>

View File

@ -1,3 +0,0 @@
/etc/config/rtorrent
/etc/cookies.txt
/root/.rtorrent.rc

View File

@ -1,8 +0,0 @@
Package: luci-app-rtorrent
Version: 0.1.6
Depends: libc, rtorrent-rpc, luaexpat, luasocket, luasec, screen
Source: https://github.com/wolandmaster/luci-app-rtorrent
Section: luci
Maintainer: Sandor Balazsi <sandor.balazsi@gmail.com>
Architecture: all
Description: rTorrent client for LuCI

View File

@ -1,6 +0,0 @@
#!/bin/sh
[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_postinst $0 $@
uci get rtorrent.logging &>/dev/null || ( uci set rtorrent.logging=rss; uci commit )
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache

View File

@ -1,5 +0,0 @@
#!/bin/sh
[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_prerm $0 $@
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache

View File

@ -1,26 +0,0 @@
All files in the source distribution of lua-bencode may be copied under the
same terms as Lua 5.0, 5.1, and 5.2. These terms are also known as the "MIT/X
Consortium License".
For reasons of clarity, a copy of these terms is included below.
Copyright (c) 2009, 2010, 2011, 2012 by Moritz Wilhelmy
Copyright (c) 2009 by Kristofer Karlsson
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,34 +0,0 @@
Lua XML-RPC is free software: it can be used for both academic and commercial
purposes at absolutely no cost. There are no royalties or GNU-like "copyleft"
restrictions. Lua XML-RPC qualifies as Open Source software. Its licenses are
compatible with GPL. Lua XML-RPC is not in the public domain and the Kepler
Project hold its copyright. The legal details are below.
The spirit of the license is that you are free to use Lua XML-RPC for any
purpose at no cost without having to ask us. The only requirement is that if
you do use Lua XML-RPC, then you should give us credit by including the
appropriate copyright notice somewhere in your product or its documentation.
The Lua XML-RPC library is designed and implemented by Roberto Ierusalimschy,
Andre Carregal and Tomas Guisasola. Tim Niemueller is the current maintainer.
The implementation is not derived from licensed software.
Copyright 2003-2010 Kepler Project.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,14 +0,0 @@
Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -1,14 +0,0 @@
# Netscape Cookie File
# From left-to-right, the cookie data consists of the following fields:
# domain - The domain that created and that can read the variable.
# tailmatch - A TRUE/FALSE value indicating if all machines within a given domain can access the variable.
# path - The path within the domain that the variable is valid for. Use / for any url.
# secure - A TRUE/FALSE value indicating if a secure connection with the domain is needed to access the variable.
# expiration - The UNIX time that the variable will expire on, max: 2147483647 (2038-01-19).
# name - The name of the variable.
# value - The value of the variable.
# domain tailmatch path secure expiration name value
.netscape.com TRUE / FALSE 2147483647 dummy 1234

View File

@ -1,17 +0,0 @@
#!/bin/sh /etc/rc.common
START=99
STOP=99
start() {
HOME=/root screen -dmS rtorrent nice -19 rtorrent
}
boot() {
start
}
stop() {
killall rtorrent
}

View File

@ -1,6 +0,0 @@
#!/bin/sh
/etc/init.d/rtorrent enable
/etc/init.d/rtorrent start
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache
exit 0

View File

@ -1,89 +0,0 @@
# This is an example resource file for rTorrent.
# Enable/modify the options as needed. Remember to uncomment
# the options you wish to enable.
# Default directory to save the downloaded torrents
#directory = /path/to/downloads/
# Default session directory
#session = /path/to/session/
# rTorrent xml-rpc scgi port
# Use the same port in luci-app-rtorrent configuration!
scgi_port = 127.0.0.1:5000
# Port range to use for listening
port_range = 21244-21244
port_random = no
# Enable DHT support for trackerless torrents or when all trackers are down.
# May be set to "disable" (completely disable DHT), "off" (do not start DHT),
# "auto" (start and stop DHT as needed), or "on" (start DHT immediately).
# The default is "off". For DHT to work, a session directory must be defined.
dht = auto
dht_port = 6881
# Enable tracker requests
trackers.enable = 1
# Set whether the client should try to connect to UDP trackers
trackers.use_udp.set = true
# Enable peer exchange (for torrents not marked private)
protocol.pex.set = yes
# Global upload and download rate in KiB. "0" for unlimited
#throttle.global_down.max_rate.set_kb = 0
#throttle.global_up.max_rate.set_kb = 0
# Maximum number of simultaneous downloads/uploads
#throttle.max_downloads.global.set = 0
#throttle.max_uploads.global.set = 0
# Maximum number of simultanious uploads per torrent
#throttle.max_uploads.set = 20
# Maximum and minimum number of peers to connect to per torrent
#throttle.min_peers.normal.set = 40
#throttle.max_peers.normal.set = 100
# Same as above but for seeding completed torrents (-1 = same as downloading)
#throttle.min_peers.seed.set = 10
#throttle.max_peers.seed.set = 50
# Check hash for finished torrents
check_hash = no
# Encryption options, set to none (default) or any combination of the following:
# allow_incoming, try_outgoing, require, require_RC4, enable_retry, prefer_plaintext
encryption = allow_incoming,try_outgoing,enable_retry
# Set the umask applied to all files created by rTorrent
system.umask.set = 022
# Support non-ascii characters in the filenames
encoding_list = UTF-8
# Maximum number of socket connections rtorrent can accept/make
#network.max_open_sockets.set = 300
# Maximum number of open files rtorrent can keep open
#network.max_open_files.set = 600
# Maximum number of simultaneous HTTP request
#network.http.max_open.set = 50
# CURL option to lower DNS timeout
#network.http.dns_cache_timeout.set = 25
# Max packet size using xmlrpc
#network.xmlrpc.size_limit.set = 2M
# rTorrent scheduler/events
schedule = rss_downloader,300,300,"execute2=/usr/lib/lua/rss_downloader.lua"
method.set_key = event.download.erased,on_erase,"branch=d.custom5=,\"execute2={rm,-rf,--,$d.base_path=}\""
# Logging
#log.execute = /path/to/log/rtorrent.execute.log
#log.xmlrpc = /path/to/log/rtorrent.xmlrpc.log

View File

@ -1,180 +0,0 @@
-- Copyright (c) 2009, 2010, 2011, 2012 by Moritz Wilhelmy
-- Copyright (c) 2009 by Kristofer Karlsson
-- Public domain lua-module for handling bittorrent-bencoded data.
-- This module includes both a recursive decoder and a recursive encoder.
local sort, concat, insert = table.sort, table.concat, table.insert
local pairs, ipairs, type, tonumber = pairs, ipairs, type, tonumber
local sub, find = string.sub, string.find
local M = {}
-- helpers
local function islist(t)
local n = #t
for k, v in pairs(t) do
if type(k) ~= "number"
or k % 1 ~= 0 -- integer?
or k < 1
or k > n
then
return false
end
end
for i = 1, n do
if t[i] == nil then
return false
end
end
return true
end
-- encoder functions
local encode_rec -- encode_list/dict and encode_rec are mutually recursive...
local function encode_list(t, x)
insert(t, "l")
for _,v in ipairs(x) do
local err,ev = encode_rec(t, v); if err then return err,ev end
end
insert(t, "e")
end
local function encode_dict(t, x)
insert(t, "d")
-- bittorrent requires the keys to be sorted.
local sortedkeys = {}
for k, v in pairs(x) do
if type(k) ~= "string" then
return "bencoding requires dictionary keys to be strings", k
end
insert(sortedkeys, k)
end
sort(sortedkeys)
for k, v in ipairs(sortedkeys) do
local err,ev = encode_rec(t, v); if err then return err,ev end
err,ev = encode_rec(t, x[v]); if err then return err,ev end
end
insert(t, "e")
end
local function encode_int(t, x)
if x % 1 ~= 0 then return "number is not an integer", x end
insert(t, "i" )
insert(t, x )
insert(t, "e" )
end
local function encode_str(t, x)
insert(t, #x )
insert(t, ":" )
insert(t, x )
end
encode_rec = function(t, x)
local typx = type(x)
if typx == "string" then return encode_str (t, x)
elseif typx == "number" then return encode_int (t, x)
elseif typx == "table" then
if islist(x) then return encode_list (t, x)
else return encode_dict (t, x)
end
else
return "type cannot be converted to an acceptable type for bencoding", typx
end
end
-- call recursive bencoder function with empty table, stringify that table.
-- this is the only encode* function visible to module users.
M.encode = function (x)
local t = {}
local err, val = encode_rec(t,x)
if not err then
return concat(t)
else
return nil, err, val
end
end
-- decoder functions
local function decode_integer(s, index)
local a, b, int = find(s, "^(%-?%d+)e", index)
if not int then return nil, "not a number", nil end
int = tonumber(int)
if not int then return nil, "not a number", int end
return int, b + 1
end
local function decode_list(s, index)
local t = {}
while sub(s, index, index) ~= "e" do
local obj, ev
obj, index, ev = M.decode(s, index)
if not obj then return obj, index, ev end
insert(t, obj)
end
index = index + 1
return t, index
end
local function decode_dictionary(s, index)
local t = {}
while sub(s, index, index) ~= "e" do
local obj1, obj2, ev
obj1, index, ev = M.decode(s, index)
if not obj1 then return obj1, index, ev end
obj2, index, ev = M.decode(s, index)
if not obj2 then return obj2, index, ev end
t[obj1] = obj2
end
index = index + 1
return t, index
end
local function decode_string(s, index)
local a, b, len = find(s, "^([0-9]+):", index)
if not len then return nil, "not a length", len end
index = b + 1
local v = sub(s, index, index + len - 1)
if #v < tonumber(len) then return nil, "truncated string at end of input", v end
index = index + len
return v, index
end
M.decode = function (s, index)
if not s then return nil, "no data", nil end
index = index or 1
local t = sub(s, index, index)
if not t then return nil, "truncation error", nil end
if t == "i" then
return decode_integer(s, index + 1)
elseif t == "l" then
return decode_list(s, index + 1)
elseif t == "d" then
return decode_dictionary(s, index + 1)
elseif t >= '0' and t <= '9' then
return decode_string(s, index)
else
return nil, "invalid type", t
end
end
return M

View File

@ -1,35 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local nixio = require "nixio"
local dm = require "luci.model.cbi.rtorrent.download"
module("luci.controller.rtorrent", package.seeall)
function index()
entry({"admin", "rtorrent"}, firstchild(), "Torrent", 45).dependent = false
entry({"admin", "rtorrent", "main"}, cbi("rtorrent/main"), "Torrent List", 10).leaf = true
entry({"admin", "rtorrent", "add"}, cbi("rtorrent/add", {autoapply=true}), "Add Torrent", 20)
entry({"admin", "rtorrent", "rss"}, arcombine(cbi("rtorrent/rss"), cbi("rtorrent/rss-rule")), "RSS Downloader", 30).leaf = true
entry({"admin", "rtorrent", "admin"}, cbi("rtorrent/admin/rtorrent"), "Admin", 40)
entry({"admin", "rtorrent", "info"}, cbi("rtorrent/torrent/info"), nil).leaf = true
entry({"admin", "rtorrent", "files"}, cbi("rtorrent/torrent/files"), nil).leaf = true
entry({"admin", "rtorrent", "trackers"}, cbi("rtorrent/torrent/trackers"), nil).leaf = true
entry({"admin", "rtorrent", "peers"}, cbi("rtorrent/torrent/peers"), nil).leaf = true
entry({"admin", "rtorrent", "download"}, call("download"), nil).leaf = true
entry({"admin", "rtorrent", "downloadall"}, call("downloadall"), nil).leaf = true
entry({"admin", "rtorrent", "admin", "rtorrent"}, cbi("rtorrent/admin/rtorrent"), nil).leaf = true
entry({"admin", "rtorrent", "admin", "rss"}, cbi("rtorrent/admin/rss"), nil).leaf = true
end
function download()
dm.download_file(nixio.bin.b64decode(luci.dispatcher.context.requestpath[4]))
end
function downloadall()
dm.download_all(nixio.bin.b64decode(luci.dispatcher.context.requestpath[4]))
end

View File

@ -1,74 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local bencode = require "bencode"
local nixio = require "nixio"
local rtorrent = require "rtorrent"
local xmlrpc = require "xmlrpc"
local common = require "luci.model.cbi.rtorrent.common"
require "luci.model.cbi.rtorrent.string"
f = SimpleForm("rtorrent", "Add Torrent")
f.submit = "Add"
local torrent
uri = f:field(TextValue, "uri", "Torrent<br />or magnet URI")
uri.rows = 1
function uri.validate(self, value, section)
if "magnet:" == string.sub(value:trim(), 1, 7) then
torrent = bencode.encode({ ["magnet-uri"] = value:trim() })
else
local ok, res = common.get(value)
if not ok then return nil, "Not able to download torrent: " .. res end
local tab, err = bencode.decode(res)
if not tab then return nil, "Not able to parse torrent file: " .. err end
torrent = res
end
return value
end
file = f:field(FileUpload, "file", "Upload torrent file")
function file.validate(self, value, section)
torrent = nixio.fs.readfile(value)
self:remove(section)
local tab, err = bencode.decode(torrent)
if not tab then return nil, "Not able to parse torrent file: " .. err end
return value
end
dir = f:field(Value, "dir", "Download directory")
dir.default = rtorrent.call("directory.default")
dir.datatype = "directory"
dir.rmempty = false
tags = f:field(Value, "tags", "Tags")
local user = luci.dispatcher.context.authuser
tags.default = "all" .. (user ~= "root" and " " .. user or "")
tags.rmempty = false
start = f:field(Flag, "start", "Start now")
start.default = "1"
start.rmempty = false
function f.handle(self, state, data)
if state == FORM_VALID and torrent and #torrent > 0 then
local params = {}
table.insert(params, data.start == "1" and "load.raw_start" or "load.raw")
table.insert(params, "") -- target
table.insert(params, xmlrpc.newTypedValue((nixio.bin.b64encode(torrent)), "base64"))
table.insert(params, "d.directory.set=\"" .. data.dir .. "\"")
table.insert(params, "d.custom1.set=\"" .. data.tags .. "\"")
if data.uri then
table.insert(params, "d.custom3.set=" .. nixio.bin.b64encode(data.uri))
end
rtorrent.call(unpack(params))
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/add"))
end
return true
end
return f

View File

@ -1,48 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local common = require "luci.model.cbi.rtorrent.common"
local nixio = require "nixio"
m = Map("rtorrent", "Admin - RSS Downloader")
s = m:section(TypedSection, "rss-feed")
s.addremove = true
s.anonymous = true
s.sortable = true
s.template = "cbi/tblsection"
s.render = function(self, section, scope)
luci.template.render("rtorrent/tabmenu", { self = {
pages = common.get_admin_pages(),
page = "RSS"
}})
TypedSection.render(self, section, scope)
end
name = s:option(Value, "name", "Name")
name.rmempty = false
url = s:option(Value, "url", "RSS Feed URL")
url.size = "65"
url.rmempty = false
enabled = s:option(Flag, "enabled", "Enabled")
enabled.rmempty = false
t = m:section(NamedSection, "logging", "rss", "Logging")
feed_logging = t:option(Flag, "feed_logging", "Enable RSS feed logging")
feed_logfile = t:option(Value, "feed_logfile", "RSS feed logfile")
feed_logfile:depends("feed_logging", 1)
function feed_logfile.validate(self, value, section)
local parent_folder = nixio.fs.dirname(value)
if parent_folder == "." or nixio.fs.stat(parent_folder, "type") ~= "dir" then
return nil, "Wrong filename, please use absolute path!"
end
return value
end
return m

View File

@ -1,126 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local rtorrent = require "rtorrent"
local common = require "luci.model.cbi.rtorrent.common"
local config = rtorrent.batchcall({
"throttle.global_up.max_rate", "throttle.global_down.max_rate",
"throttle.max_downloads.global", "throttle.max_uploads.global",
"throttle.max_uploads",
"throttle.min_peers.normal", "throttle.max_peers.normal",
"throttle.min_peers.seed", "throttle.max_peers.seed"
})
local function set_config(key, value)
if tonumber(value) ~= config[key] then
rtorrent.call(key .. ".set", "", value)
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/admin/rtorrent"))
end
end
f = SimpleForm("rtorrent", "Admin - rTorrent")
speed = f:section(SimpleSection)
speed.title = "Bandwidth limits"
speed.render = function(self, ...)
luci.template.render("rtorrent/tabmenu", { self = {
pages = common.get_admin_pages(),
page = "rTorrent"
}})
SimpleSection.render(self, ...)
end
upload_rate = speed:option(Value, "upload_rate", "Upload limit (KiB/sec)",
"Global upload rate (0: unlimited)")
upload_rate.rmempty = false
upload_rate.default = config["throttle.global_up.max_rate"] / 1024
upload_rate.datatype = "uinteger"
function upload_rate.write(self, section, value)
set_config("throttle.global_up.max_rate", value .. "k")
end
download_rate = speed:option(Value, "download_rate", "Download limit (KiB/sec)",
"Global downlaod rate (0: unlimited)")
download_rate.rmempty = false
download_rate.default = config["throttle.global_down.max_rate"] / 1024
download_rate.datatype = "uinteger"
function download_rate.write(self, section, value)
set_config("throttle.global_down.max_rate", value .. "k")
end
global_limits = f:section(SimpleSection)
global_limits.title = "Global limits"
max_downloads_global = global_limits:option(Value, "max_downloads_global", "Download slots",
"Maximum number of simultaneous downloads")
max_downloads_global.rmempty = false
max_downloads_global.default = config["throttle.max_downloads.global"]
max_downloads_global.datatype = "uinteger"
function max_downloads_global.write(self, section, value)
set_config("throttle.max_downloads.global", value)
end
max_uploads_global = global_limits:option(Value, "max_uploads_global", "Upload slots",
"Maximum number of simultaneous uploads")
max_uploads_global.rmempty = false
max_uploads_global.default = config["throttle.max_uploads.global"]
max_uploads_global.datatype = "uinteger"
function max_uploads_global.write(self, section, value)
set_config("throttle.max_uploads.global", value)
end
torrent_limits = f:section(SimpleSection)
torrent_limits.title = "Torrent limits"
max_uploads = torrent_limits:option(Value, "max_uploads", "Maximum uploads",
"Maximum number of simultanious uploads per torrent")
max_uploads.rmempty = false
max_uploads.default = config["throttle.max_uploads"]
max_uploads.datatype = "uinteger"
function max_uploads.write(self, section, value)
set_config("throttle.max_uploads", value)
end
min_peers = torrent_limits:option(Value, "min_peers", "Minimum peers",
"Minimum number of peers to connect to per torrent")
min_peers.rmempty = false
min_peers.default = config["throttle.min_peers.normal"]
min_peers.datatype = "uinteger"
function min_peers.write(self, section, value)
set_config("throttle.min_peers.normal", value)
end
max_peers = torrent_limits:option(Value, "max_peers", "Maximum peers",
"Maximum number of peers to connect to per torrent")
max_peers.rmempty = false
max_peers.default = config["throttle.max_peers.normal"]
max_peers.datatype = "uinteger"
function max_peers.write(self, section, value)
set_config("throttle.max_peers.normal", value)
end
min_peers_seed = torrent_limits:option(Value, "min_peers_seed", "Minimum seeds",
"Minimum number of seeds for completed torrents (-1 = same as peers)")
min_peers_seed.rmempty = false
min_peers_seed.default = config["throttle.min_peers.seed"]
min_peers_seed.datatype = "integer"
function min_peers_seed.write(self, section, value)
set_config("throttle.min_peers.seed", value)
end
max_peers_seed = torrent_limits:option(Value, "max_peers_seed", "Maximum seeds",
"Maximum number of seeds for completed torrents (-1 = same as peers)")
max_peers_seed.rmempty = false
max_peers_seed.default = config["throttle.max_peers.seed"]
max_peers_seed.datatype = "integer"
function max_peers_seed.write(self, section, value)
set_config("throttle.max_peers.seed", value)
end
-- dir = f:field(DummyValue, "dummy", luci.dispatcher.context.authuser)
return f

View File

@ -1,116 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local http = require "socket.http"
local https = require "ssl.https"
local ssl = require "ssl"
local ltn12 = require "ltn12"
local fs = require "nixio.fs"
local dispatcher = require "luci.dispatcher"
require "luci.model.cbi.rtorrent.string"
local string, os, io, math, assert = string, os, io, math, assert
local ipairs, table, unpack, tonumber = ipairs, table, unpack, tonumber
local COOKIES_FILE = "/etc/cookies.txt"
module "luci.model.cbi.rtorrent.common"
function get_torrent_pages(hash)
return {
{ name = "Info", link = dispatcher.build_url("admin/rtorrent/info/") .. hash },
{ name = "File List", link = dispatcher.build_url("admin/rtorrent/files/") .. hash },
{ name = "Tracker List", link = dispatcher.build_url("admin/rtorrent/trackers/") .. hash },
{ name = "Peer List", link = dispatcher.build_url("admin/rtorrent/peers/") .. hash }
}
end
function get_admin_pages()
return {
{ name = "rTorrent", link = dispatcher.build_url("admin/rtorrent/admin/rtorrent") },
{ name = "RSS", link = dispatcher.build_url("admin/rtorrent/admin/rss") }
}
end
function human_size(bytes)
local symbol = {[0]="B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
local exp = bytes > 0 and math.floor(math.log(bytes) / math.log(1024)) or 0
local value = bytes / math.pow(1024, exp)
local acc = bytes > 0 and 2 - math.floor(math.log10(value)) or 2
if acc < 0 then acc = 0 end
return string.format("%." .. acc .. "f " .. symbol[exp], value)
end
function human_time(sec)
local t = os.date("*t", sec)
if t["day"] > 25 then return "&#8734;"
elseif t["day"] > 1 then return string.format("%dd<br />%dh %dm", t["day"] - 1, t["hour"], t["min"])
elseif t["hour"] > 1 then return string.format("%dh<br />%dm %ds", t["hour"] - 1, t["min"], t["sec"])
elseif t["min"] > 0 then return string.format("%dm %ds", t["min"], t["sec"])
else return string.format("%ds", t["sec"]) end
end
function div(body, ...)
local class = {}
for _, c in ipairs({...}) do
if c then table.insert(class, c) end
end
if #class > 0 then
return "<div class=\"%s\">%s</div>" % {table.concat(class, " "), body}
else
return body
end
end
function wget(url)
local file = assert(io.popen("/usr/bin/wget "
.. "--quiet "
.. "--user-agent=unknown "
.. "--referer=http://www.google.com "
.. "--load-cookies=" .. COOKIES_FILE .. " "
.. "--output-document=- "
.. url, "r"))
local response = file:read("*all")
file:close()
return response
end
function get(url)
local response = {}
local proto = url:starts("https://") and https or http
proto.TIMEOUT = 5
local body, code, headers, status = proto.request {
method = "GET",
headers = {
["Referer"] = "http://www.google.com",
["User-Agent"] = "unknown",
["Cookie"] = get_cookies(url)
},
url = url,
redirect = (proto.PORT == 80) and true or nil,
sink = ltn12.sink.table(response)
}
if code == 301 then return get(headers["location"]) end
if code == 200 then return true, table.concat(response)
else
local body = wget(url)
if body:len() > 0 then return true, body
else return false, status end
end
end
function get_cookies(url)
local cookies = {}
for _, line in ipairs(fs.readfile(COOKIES_FILE):split("\n\r")) do
if not line:match("^\s*#.*") then
local domain, tailmatch, path, secure, expiration, name, value = unpack(line:split())
local url_match = (secure == "TRUE") and "^https://" or "^https?://"
url_match = url_match .. (tailmatch == "TRUE" and ".*" or "") .. domain .. path
if url:match(url_match) and tonumber(expiration) >= os.time() then
table.insert(cookies, name .. "=" .. value)
end
end
end
return table.concat(cookies, "; ")
end

View File

@ -1,66 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local nixio = require "nixio"
local http = require "luci.http"
local ltn12 = require "luci.ltn12"
local sys = require "luci.controller.admin.system"
local common = require "luci.model.cbi.rtorrent.common"
require "luci.model.cbi.rtorrent.string"
local ipairs, string = ipairs, string
local PROTECTED_PATH = {"/bin", "/dev", "/etc", "/lib", "/overlay", "/root", "/sbin", "/tmp", "/usr", "/var", "/www"}
module("luci.model.cbi.rtorrent.download", package.seeall)
function security_check(file)
for _, path_prefix in ipairs(PROTECTED_PATH) do
if file:starts(path_prefix) then
http.write("<h1>Access Denied</h1>")
return false
end
end
return true
end
function download_file(file)
file = nixio.fs.realpath(file)
if security_check(file) then
local f = nixio.open(file, "r")
http.header('Content-Disposition', 'attachment; filename="%s"' % nixio.fs.basename(file))
http.header('Content-Length', nixio.fs.stat(file, "size"))
http.prepare_content("application/octet-stream")
repeat
local buf = f:read(2^13) -- 8k
http.write(buf)
until (buf == "")
f:close()
end
end
function download_all(path)
path = nixio.fs.realpath(path)
if security_check(path) then
if string.find(string.lower(http.getenv("HTTP_USER_AGENT")), "linux") then
download_all_as_tar(path)
else
download_all_as_zip(path)
end
end
end
function download_all_as_zip(path)
local reader = sys.ltn12_popen("zip -0 -j -r - \"%s\"" % path)
http.header('Content-Disposition', 'attachment; filename="%s.zip"' % nixio.fs.basename(path))
http.prepare_content("application/zip")
ltn12.pump.all(reader, http.write)
end
function download_all_as_tar(path)
local reader = sys.ltn12_popen("tar -cf - -C \"%s\" ." % path)
http.header('Content-Disposition', 'attachment; filename="%s.tar"' % nixio.fs.basename(path))
http.prepare_content("application/x-tar")
ltn12.pump.all(reader, http.write)
end

View File

@ -1,288 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
-- Custom fields:
-- d.custom1: tags (space delimited)
-- d.custom2: tracker favicon
-- d.custom3: url of torrent file
-- d.custom4: not used
-- d.custom5: when "1": delete files from disk on erase
local common = require "luci.model.cbi.rtorrent.common"
local rtorrent = require "rtorrent"
require "luci.model.cbi.rtorrent.string"
local selected, format, total = {}, {}, {}
local methods = { "hash", "name", "size_bytes", "bytes_done", "hashing", "state", "complete",
"peers_accounted", "peers_complete", "down.rate", "up.rate", "ratio", "up.total",
"timestamp.started", "timestamp.finished", "custom1", "custom2" }
function status(d)
if d["hashing"] > 0 then return "hash"
elseif d["state"] == 0 then return "stop"
elseif d["state"] > 0 then
if d["complete"] == 0 then return "down"
else return "seed" end
else return "unknown" end
end
function eta(d)
if d["bytes_done"] < d["size_bytes"] then
if d["down_rate"] > 0 then
return common.human_time((d["size_bytes"] - d["bytes_done"]) / d["down_rate"])
else return "&#8734;" end
else return "--" end
end
function favicon(d)
if not d["custom2"] or d["custom2"] == "" then
d["custom2"] = "/luci-static/resources/icons/unknown_tracker.png"
for _, t in pairs(rtorrent.multicall("t.", d["hash"], 0, "url", "is_enabled")) do
if t["is_enabled"] then
local domain = t["url"]:match("[%w%.:/]*[%./](%w+%.%w+)")
if domain then
local icon = "http://" .. domain .. "/favicon.ico"
if common.get(icon) then
d["custom2"] = icon
break
end
end
end
end
rtorrent.call("d.custom2.set", d["hash"], d["custom2"])
end
return d["custom2"]
end
function has_tag(tags, tag)
for _, t in ipairs(tags) do
if t.name:lower() == tag:lower() then return true end
end
return false
end
function get_tags(details)
local l = {}
local has_incomplete = false
for _, d in ipairs(details) do
for _, p in ipairs(d["custom1"]:split()) do
if not has_tag(l, p) then
table.insert(l, {name = p:ucfirst(), link = luci.dispatcher.build_url("admin/rtorrent/main/%s" % p)})
end
end
if d["complete"] == 0 then
has_incomplete = true
end
end
if has_incomplete then
table.insert(l, {name = "Incomplete", link = luci.dispatcher.build_url("admin/rtorrent/main/incomplete")})
end
return l
end
function filter(details, page)
local filtered = {}
for _, d in ipairs(details) do
if string.find(" " .. d["custom1"] .. " ", " " .. page .. " ") then
table.insert(filtered, d)
end
if page == "incomplete" and d["complete"] == 0 then
table.insert(filtered, d)
end
end
return filtered
end
function format.icon(d, v)
return "<img src=\"" .. v .. "\" />"
end
function format.name(d, v)
total["name"] = (total["name"] or 0) + 1
local url = luci.dispatcher.build_url("admin/rtorrent/files/" .. d["hash"])
return "<a href=\"%s\">%s</a>" % {url, v}
end
function format.size_bytes(d, v)
total["size_bytes"] = (total["size_bytes"] or 0) + v
return "<div title=\"%s B\">%s</div>" % {v, common.human_size(v)}
end
function format.done_percent(d, v)
return string.format("%.1f%%", v)
end
function format.status(d, v)
return common.div(v, v == "stop" and "red", v == "seed" and "blue",
v == "down" and "green", v == "hash" and "green")
end
function format.down_rate(d, v)
total["down_rate"] = (total["down_rate"] or 0) + v
return string.format("%.2f", v / 1000)
end
function format.up_rate(d, v)
total["up_rate"] = (total["up_rate"] or 0) + v
return string.format("%.2f", v / 1000)
end
function format.ratio(d, v)
return common.div(string.format("%.2f", v / 1000), v < 1000 and "red" or "green")
-- "title: Total uploaded: " .. common.human_size(d["up_total"]))
end
function format.eta(d, v)
local download_started = d["timestamp_started"] == 0
and "not yet started" or os.date("!%Y-%m-%d %H:%M:%S", d["timestamp_started"])
local download_finished = d["timestamp_finished"] == 0
and "not yet finished" or os.date("!%Y-%m-%d %H:%M:%S", d["timestamp_finished"])
return "<div title=\"Download started: %s&#13;Download finished: %s\">%s</div>" % {
download_started, download_finished, v }
end
function add_custom(details)
for _, d in ipairs(details) do
-- refactor: swap favicon (custom1) and tags (custom2)
if d["custom1"]:ends(".ico") or d["custom1"]:ends(".png") then
local temp = d["custom1"]
d["custom1"] = d["custom2"]
d["custom2"] = temp
rtorrent.call("d.custom1.set", d["hash"], d["custom1"])
rtorrent.call("d.custom2.set", d["hash"], d["custom2"])
end
d["status"] = status(d)
d["done_percent"] = 100.0 * d["bytes_done"] / d["size_bytes"]
d["eta"] = eta(d)
d["icon"] = favicon(d)
d["custom1"] = (d["custom1"] == "") and "all" or d["custom1"]
end
end
function add_summary(details)
table.insert(details, {
["name"] = "TOTAL: " .. total["name"] .. " pcs.",
["size_bytes"] = common.human_size(total["size_bytes"]),
["down_rate"] = string.format("%.2f", total["down_rate"] / 1000),
["up_rate"] = string.format("%.2f", total["up_rate"] / 1000),
["select"] = "%hidden%"
})
end
function html_format(details)
table.sort(details, function(a, b) return a["name"] < b["name"] end)
for _, d in ipairs(details) do
for m, v in pairs(d) do
d[m] = format[m] and format[m](d, v) or tostring(v)
end
end
end
f = SimpleForm("rtorrent", "Torrent List")
f.reset = false
f.submit = false
local details = rtorrent.multicall("d.", "default", unpack(methods))
add_custom(details)
local tags = get_tags(details)
local user = luci.dispatcher.context.authuser
local page = arg[1] or (has_tag(tags, user) and user or "all")
local filtered = filter(details, page)
html_format(filtered)
if #filtered > 1 then add_summary(filtered) end
t = f:section(Table, filtered)
t.template = "rtorrent/list"
t.pages = tags
t.page = page
t.headcol = 2
AbstractValue.tooltip = function(self, s) self.hint = s return self end
t:option(DummyValue, "icon").rawhtml = true
t:option(DummyValue, "name", "Name").rawhtml = true
t:option(DummyValue, "size_bytes", "Size"):tooltip("Full size of torrent").rawhtml = true
t:option(DummyValue, "done_percent", "Done"):tooltip("Download done percent").rawhtml = true
t:option(DummyValue, "status", "Status").rawhtml = true
t:option(DummyValue, "peers_accounted", "&uarr;"):tooltip("Seeder count").rawhtml = true
t:option(DummyValue, "peers_complete", "&darr;"):tooltip("Leecher count").rawhtml = true
t:option(DummyValue, "down_rate", "Down<br />Speed"):tooltip("Download speed in kb/s").rawhtml = true
t:option(DummyValue, "up_rate", "Up<br />Speed"):tooltip("Upload speed in kb/s").rawhtml = true
t:option(DummyValue, "ratio", "Ratio"):tooltip("Download/upload ratio").rawhtml = true
t:option(DummyValue, "eta", "ETA"):tooltip("Estimated Time of Arrival").rawhtml = true
select = t:option(Flag, "select")
select.template = "rtorrent/fvalue"
function select.write(self, section, value)
table.insert(selected, filtered[section].hash)
end
s = f:section(SimpleSection)
s.template = "rtorrent/buttonsection"
s.style = "float: right;"
start = s:option(Button, "start", "start")
start.template = "rtorrent/button"
start.inputstyle = "apply"
function start.write(self, section, value)
if next(selected) ~= nil then
for _, hash in ipairs(selected) do
rtorrent.call("d.open", hash)
rtorrent.call("d.start", hash)
end
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/main/" .. page))
end
end
stop = s:option(Button, "stop", "stop")
stop.template = "rtorrent/button"
stop.inputstyle = "reset"
function stop.write(self, section, value)
if next(selected) ~= nil then
for _, hash in ipairs(selected) do
rtorrent.call("d.stop", hash)
rtorrent.call("d.close", hash)
end
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/main/" .. page))
end
end
remove = s:option(Button, "remove", "remove")
remove.template = "rtorrent/button"
remove.inputstyle = "remove"
function remove.write(self, section, value)
if next(selected) ~= nil then
for _, hash in ipairs(selected) do
rtorrent.call("d.close", hash)
rtorrent.call("d.erase", hash)
end
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/main/" .. page))
end
end
r = f:section(SimpleSection)
r.template = "rtorrent/buttonsection"
r.style = "float: right;"
delete = r:option(Button, "delete", "remove and delete data")
delete.template = "rtorrent/button"
delete.inputstyle = "remove"
function delete.write(self, section, value)
if next(selected) ~= nil then
for _, hash in ipairs(selected) do
rtorrent.call("d.custom5.set", hash, "1")
rtorrent.call("d.close", hash)
rtorrent.call("d.erase", hash)
end
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/main/" .. page))
end
end
return f

View File

@ -1,52 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local rtorrent = require "rtorrent"
m = Map("rtorrent")
m.redirect = luci.dispatcher.build_url("admin/rtorrent/rss")
local name = m:get(arg[1], "name")
m.title = "%s - %s" %{"RSS Downloader", name or "(Unnamed rule)"}
s = m:section(NamedSection, arg[1], "rss-rule")
s.anonymous = true
s.addremove = false
enabled = s:option(Flag, "enabled", "Enabled")
enabled.rmempty = false
name = s:option(Value, "name", "Name")
name.rmempty = false
match = s:option(TextValue, "match", "Match (lua regex)")
match.template = "rtorrent/tvalue"
match.rmempty = false
match.rows = 1
exclude = s:option(TextValue, "exclude", "Exclude (lua regex)")
exclude.rows = 1
s:option(Value, "minsize", "Min size (MiB):")
s:option(Value, "maxsize", "Max size (MiB):")
feed = s:option(MultiValue, "feed", "Feed")
feed.delimiter = ";"
m.uci:foreach(m.config, "rss-feed", function(f)
feed:value(f.name, f.name)
end)
tags = s:option(Value, "tags", "Add tags")
tags.default = "all"
destdir = s:option(Value, "destdir", "Download directory")
destdir.default = rtorrent.call("directory.default")
destdir.datatype = "directory"
destdir.rmempty = false
autostart = s:option(Flag, "autostart", "Start download")
autostart.default = "1"
autostart.rmempty = false
return m

View File

@ -1,39 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
m = Map("rtorrent", "RSS Downloader")
s = m:section(TypedSection, "rss-rule")
s.addremove = true
s.anonymous = true
s.sortable = true
s.template = "cbi/tblsection"
s.extedit = luci.dispatcher.build_url("admin/rtorrent/rss/%s")
s.template_addremove = "rtorrent/rss_addrule"
function s.parse(self, ...)
TypedSection.parse(self, ...)
local newrule_name = m:formvalue("_newrule.name")
local newrule_submit = m:formvalue("_newrule.submit")
if newrule_submit then
newrule = TypedSection.create(self, section)
self.map:set(newrule, "name", newrule_name)
m.uci:save("rtorrent")
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/rss", newrule))
end
end
name = s:option(DummyValue, "name", "Name")
name.width = "30%"
match = s:option(DummyValue, "match", "Match")
match.width = "60%"
enabled = s:option(Flag, "enabled", "Enabled")
enabled.rmempty = false
return m

View File

@ -1,30 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
function string.starts(str, begin)
if not str then return false end
return string.sub(str, 1, string.len(begin)) == begin
end
function string.ends(str, tail)
if not str then return false end
return string.sub(str, -string.len(tail)) == tail
end
function string.split(str, sep)
if sep == nil then sep = "%s" end
local t = {}
for s in str:gmatch("([^" .. sep .. "]+)") do
table.insert(t, s)
end
return t
end
function string.ucfirst(str)
return (str:gsub("^%l", string.upper))
end
function string.trim(str)
return str:match("^()%s*$") and "" or str:match("^%s*(.*%S)")
end

View File

@ -1,138 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local rtorrent = require "rtorrent"
local nixio = require "nixio"
local common = require "luci.model.cbi.rtorrent.common"
local hash = arg[1]
local details = rtorrent.batchcall({"name", "base_path"}, hash, "d.")
local files = rtorrent.multicall("f.", hash, 0, "path", "path_depth", "path_components", "size_bytes",
"size_chunks", "completed_chunks", "priority", "frozen_path")
local format, total = {}, {}
function format.icon(r, v)
local icon_path = "/luci-static/resources/icons/filetypes"
local ext = v:match("%.([^%.]+)$")
if ext and nixio.fs.stat("/www/%s/%s.png" % {icon_path, ext:lower()}, "type") then
return "%s/%s.png" % {icon_path, ext:lower()}
end
return "%s/%s.png" % {icon_path, "file"}
end
function format.dir(r, v)
return "<img style=\"vertical-align: text-top;\" src=\"/luci-static/resources/icons/filetypes/dir.png\" /> " .. v
end
function format.file(r, v)
total["name"] = (total["name"] or 0) + 1
local url = luci.dispatcher.build_url("admin/rtorrent/download/") .. nixio.bin.b64encode(r["frozen_path"])
local link = r["chunks_percent"] == 100 and "<a href=\"" .. url .. "\">" .. v .. "</a>" or v
return "<img style=\"vertical-align: middle;\" src=\"" .. format["icon"](r, v) .. "\" /> " .. link
end
function format.size_bytes(r, v)
total["size_bytes"] = (total["size_bytes"] or 0) + v
return common.human_size(v)
end
function format.chunks_percent(r, v)
return common.div(string.format("%.1f%%", v), v < 100 and "red")
end
function format.priority(r, v)
return tostring(v)
end
function add_custom(files)
for i, r in ipairs(files) do
r["id"] = i
r["chunks_percent"] = r["completed_chunks"] * 100.0 / r["size_chunks"]
end
end
function add_summary(list)
table.insert(list, {
["name"] = "TOTAL: " .. total["name"] .. " pcs.",
["size_bytes"] = common.human_size(total["size_bytes"]),
["priority"] = "%hidden%"
})
end
function path_compare(a, b)
if a["path_depth"] ~= b["path_depth"] and (a["path_depth"] == 1 or b["path_depth"] == 1) then
return a["path_depth"] > b["path_depth"]
end
return a["path"] < b["path"]
end
local list, last_path = {}, {}
add_custom(files)
table.sort(files, path_compare)
for _, r in ipairs(files) do
for i, p in ipairs(r["path_components"]) do
if last_path[i] ~= p then
local t = i == #r["path_components"] and "file" or "dir"
local n = {}
if t == "file" then
for m, v in pairs(r) do
n[m] = format[m] and format[m](r, v) or tostring(v)
end
else
n["priority"] = "%hidden%"
end
n["name"] = string.rep("&emsp;", i - 1) .. format[t](r, p)
table.insert(list, n)
end
last_path[i] = p
end
end
f = SimpleForm("rtorrent", details["name"])
f.redirect = luci.dispatcher.build_url("admin/rtorrent/main")
if nixio.fs.stat(details["base_path"], "type") == "dir" and table.getn(list) > 1 then
f.cancel = "Download all"
else
f.cancel = false
end
if #list > 1 then add_summary(list) end
t = f:section(Table, list)
t.template = "rtorrent/list"
t.pages = common.get_torrent_pages(hash)
t.page = "File List"
AbstractValue.tooltip = function(self, s) self.hint = s return self end
t:option(DummyValue, "name", "Name").rawhtml = true
t:option(DummyValue, "size_bytes", "Size")
t:option(DummyValue, "chunks_percent", "Done"):tooltip("Download done percent").rawhtml = true
prio = t:option(ListValue, "priority", "Priority"):tooltip("Rotate priority")
prio.template = "rtorrent/lvalue"
prio.onclick = [[
var inputs = document.getElementsByClassName("cbi-input-select");
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].selectedIndex < inputs[i].length - 1) {
inputs[i].selectedIndex++;
} else {
inputs[i].selectedIndex = 0;
}
}
]]
prio:value("0", "off")
prio:value("1", "normal")
prio:value("2", "high")
function prio.write(self, section, value)
rtorrent.call("f.priority.set", hash .. ":f" .. (list[tonumber(section)].id - 1), tonumber(value))
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/files/%s" % hash))
end
function f:on_cancel()
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/downloadall/")
.. nixio.bin.b64encode(details["base_path"]))
end
return f

View File

@ -1,49 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local rtorrent = require "rtorrent"
local common = require "luci.model.cbi.rtorrent.common"
local hash = arg[1]
local details = rtorrent.batchcall({"name", "custom1", "timestamp.started", "timestamp.finished"}, hash, "d.")
f = SimpleForm("rtorrent", details["name"])
f.redirect = luci.dispatcher.build_url("admin/rtorrent/main")
t = f:section(Table, list)
t.template = "rtorrent/list"
t.pages = common.get_torrent_pages(hash)
t.page = "Info"
hash_id = f:field(DummyValue, "hash", "Hash")
function hash_id.cfgvalue(self, section)
return hash
end
started = f:field(DummyValue, "started", "Download started")
started.value = details["timestamp.started"] == 0
and "not yet started"
or os.date("!%Y-%m-%d %H:%M:%S", details["timestamp.started"])
finished = f:field(DummyValue, "finished", "Download finished")
finished.value = details["timestamp.finished"] == 0
and "not yet finished"
or os.date("!%Y-%m-%d %H:%M:%S", details["timestamp.finished"])
tags = f:field(Value, "tags", "Tags")
tags.default = details["custom1"]
tags.rmempty = false
function tags.write(self, section, value)
rtorrent.call("d.custom1.set", hash, value)
end
function f.handle(self, state, data)
if state == FORM_VALID then
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/info/") .. hash)
end
return true
end
return f

View File

@ -1,118 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local rtorrent = require "rtorrent"
local http = require "socket.http"
local common = require "luci.model.cbi.rtorrent.common"
local hash = arg[1]
local details = rtorrent.batchcall({"name"}, hash, "d.")
local format, total, map = {}, {}, {}
local geoplugin_net = {
address = "http://www.geoplugin.net/json.gp?ip=%s",
fields = { geoplugin_countryCode = "country_code", geoplugin_countryName = "country", geoplugin_region = "region",
geoplugin_city = "city", geoplugin_latitude = "latitude", geoplugin_longitude = "longitude"
}}
local ip2geo = geoplugin_net
function map.googlemap(latitude, longitude, zoom)
return "https://google.com/maps/place/%s,%s/@%s,%s,%sz" % {latitude, longitude, latitude, longitude, zoom}
end
function map.openstreetmap(latitude, longitude, zoom)
return "http://www.openstreetmap.org/?mlat=%s&mlon=%s#map=%s/%s/%s/m" % {latitude, longitude, zoom, latitude, longitude}
end
function format.address(r, v)
total["address"] = (total["address"] or 0) + 1
local map = map.googlemap(r.latitude, r.longitude, 11)
-- local map = map.openstreetmap(r.latitude, r.longitude, 11)
-- local flag = "<img src=\"http://www.iplocation.net/images/flags/%s.gif\" />" % r.country_code:lower()
local flag = "<img src=\"http://static.hltv.org/images/flag/%s.gif\" />" % r.country_code:lower()
return "%s <a href=\"%s\" target=\"_blank\">%s</a>" % {flag, map, v}
end
function format.completed_percent(r, v)
return string.format("%.1f%%", v)
end
function format.down_rate(d, v)
total["down_rate"] = (total["down_rate"] or 0) + v
return string.format("%.2f", v / 1000)
end
function format.up_rate(d, v)
total["up_rate"] = (total["up_rate"] or 0) + v
return string.format("%.2f", v / 1000)
end
function format.down_total(d, v)
return "<div title=\"%s B\">%s</div>" % {v, common.human_size(v)}
end
function format.up_total(d, v)
return format.down_total(d, v)
end
function json2table(json)
loadstring("j2t = " .. string.gsub(string.gsub(json, '([,%{])%s*\n?%s*"', '%1["'), '"%s*:%s*', '"]='))()
return j2t
end
function add_location(r)
for i, j in pairs(json2table(http.request(ip2geo.address % r.address))) do
if ip2geo.fields[i] then r[ip2geo.fields[i]] = j end
end
local location = {}
for _, k in ipairs({"country", "region", "city"}) do
if r[k] ~= "" and not tonumber(r[k]) then
table.insert(location, (r[k]:gsub("\u(%x%x%x%x)", "&#x%1")))
end
end
r["location"] = table.concat(location, "/")
end
function add_summary(list)
table.insert(list, {
["address"] = "TOTAL: " .. total["address"] .. " pcs.",
["down_rate"] = string.format("%.2f", total["down_rate"] / 1000),
["up_rate"] = string.format("%.2f", total["up_rate"] / 1000)
})
end
local list = rtorrent.multicall("p.", hash, 0, "address", "completed_percent", "client_version",
"down_rate", "up_rate", "up_total", "down_total")
for _, r in ipairs(list) do
add_location(r)
for k, v in pairs(r) do
r[k] = format[k] and format[k](r, v) or tostring(v)
end
end
f = SimpleForm("rtorrent", details["name"])
f.redirect = luci.dispatcher.build_url("admin/rtorrent/main")
f.reset = false
f.submit = false
if #list > 1 then add_summary(list) end
t = f:section(Table, list)
t.template = "rtorrent/list"
t.pages = common.get_torrent_pages(hash)
t.page = "Peer List"
AbstractValue.tooltip = function(self, s) self.hint = s return self end
t:option(DummyValue, "address", "Address"):tooltip("Peer IP address").rawhtml = true
t:option(DummyValue, "client_version", "Client"):tooltip("Client version")
t:option(DummyValue, "location", "Location"):tooltip("Location: country/region/city").rawhtml = true
t:option(DummyValue, "completed_percent", "Done"):tooltip("Download done percent")
t:option(DummyValue, "down_rate", "Down<br />Speed"):tooltip("Download speed in kb/s")
t:option(DummyValue, "up_rate", "Up<br />Speed"):tooltip("Upload speed in kb/s")
t:option(DummyValue, "down_total", "Downloaded"):tooltip("Total downloaded").rawhtml = true
t:option(DummyValue, "up_total", "Uploaded"):tooltip("Total uploaded").rawhtml = true
return f

View File

@ -1,90 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local rtorrent = require "rtorrent"
local common = require "luci.model.cbi.rtorrent.common"
local hash = arg[1]
local details = rtorrent.batchcall({"name"}, hash, "d.")
local format, total = {}, {}
function format.url(r, v)
total["url"] = (total["url"] or 0) + 1
end
function format.scrape_downloaded(r, v)
total["scrape_downloaded"] = (total["scrape_downloaded"] or 0) + v
return tostring(v)
end
function format.scrape_complete(r, v)
total["scrape_complete"] = (total["scrape_complete"] or 0) + v
return tostring(v)
end
function format.scrape_incomplete(r, v)
total["scrape_incomplete"] = (total["scrape_incomplete"] or 0) + v
return tostring(v)
end
function format.scrape_time_last(r, v)
return common.human_time(os.time() - v)
end
function add_summary(list)
table.insert(list, {
["url"] = "TOTAL: " .. total["url"] .. " pcs.",
["scrape_downloaded"] = tostring(total["scrape_downloaded"]),
["scrape_complete"] = tostring(total["scrape_complete"]),
["scrape_incomplete"] = tostring(total["scrape_incomplete"]),
["is_enabled"] = "%hidden%"
})
end
local list = rtorrent.multicall("t.", hash, 0, "is_enabled", "url", "scrape_downloaded", "scrape_complete",
"scrape_incomplete", "scrape_time_last")
for _, r in ipairs(list) do
for k, v in pairs(r) do
r[k] = format[k] and format[k](r, v) or tostring(v)
end
end
f = SimpleForm("rtorrent", details["name"])
f.redirect = luci.dispatcher.build_url("admin/rtorrent/main")
if #list > 1 then add_summary(list) end
t = f:section(Table, list)
t.template = "rtorrent/list"
t.pages = common.get_torrent_pages(hash)
t.page = "Tracker List"
AbstractValue.tooltip = function(self, s) self.hint = s return self end
t:option(DummyValue, "url", "Url")
t:option(DummyValue, "scrape_downloaded", "D"):tooltip("Downloaded")
t:option(DummyValue, "scrape_complete", "S"):tooltip("Seeders")
t:option(DummyValue, "scrape_incomplete", "L"):tooltip("Leechers")
t:option(DummyValue, "scrape_time_last", "Updated"):tooltip("Last update time").rawhtml = true
enabled = t:option(Flag, "is_enabled", "Enabled")
enabled.template = "rtorrent/fvalue"
enabled.rmempty = false
enabled.rawhtml = true
function enabled.write(self, section, value)
if value ~= tostring(list[section].is_enabled) then
rtorrent.call("t.is_enabled.set", hash .. ":t" .. (section - 1), tonumber(value))
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/trackers/%s" % hash))
end
end
add = f:field(Value, "add_tracker", "Add tracker")
function add.write(self, section, value)
rtorrent.call("d.tracker.insert", hash, table.getn(list), value)
luci.http.redirect(luci.dispatcher.build_url("admin/rtorrent/trackers/%s" % hash))
end
return f

View File

@ -1,3 +0,0 @@
<%+cbi/valueheader%>
<input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. ifattr(self.style, "style") .. attr("value", self:cfgvalue(section) or self.inputtitle or self.title)%> />
<%+cbi/valuefooter%>

View File

@ -1,5 +0,0 @@
<fieldset class="cbi-section" style="padding-top: 0">
<div class="cbi-section-node" id="cbi-<%=self.config%>-<%=tostring(self):sub(8)%>"<%= ifattr(self.style, "style")%>>
<% self:render_children(1, { valueheader = "rtorrent/empty", valuefooter = "rtorrent/empty" }) %>
</div>
</fieldset>

View File

@ -1,11 +0,0 @@
<%+cbi/valueheader%>
<% if not self:cfgvalue(section) or self:cfgvalue(section) ~= "%hidden%" then -%>
<input type="hidden" value="1"<%=
attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option)
%> />
<input class="cbi-input-checkbox" onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)" type="checkbox"<%=
attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
%> />
<%- end %>
<%+cbi/valuefooter%>

View File

@ -1,51 +0,0 @@
<%-
local rowcnt = 1
function rowstyle()
rowcnt = rowcnt + 1
return (rowcnt % 2) + 1
end
-%>
<style type="text/css">
.center { text-align: center }
.nowrap { white-space: nowrap }
.wrap { word-break: break-all; min-width: 100px }
.red { color: #b20000 }
.green { color: #00a100 }
.blue { color: #0000bf }
.bold { font-weight: bold }
table.cbi-section-table a:link { color: #404040 }
table.cbi-section-table a:visited { color: #404040 }
</style>
<%+rtorrent/tabmenu%>
<fieldset class="cbi-section" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
<div class="cbi-section-descr"><%=self.description%></div>
<div class="cbi-section-node">
<table class="cbi-section-table">
<tr class="cbi-section-table-titles">
<%- for i, k in pairs(self.children) do -%>
<th class="cbi-section-table-cell<% if i ~= (self.headcol or 1) then %> center nowrap<% else %> wrap<% end -%>
"<%=ifattr(k.hint, "title", k.hint)%><%=ifattr(k.onclick, "onclick", k.onclick)%>><%=k.title%></th>
<%- end -%>
</tr>
<%- for j, k in ipairs(self:cfgsections()) do
section = k
scope = { valueheader = "rtorrent/empty", valuefooter = "rtorrent/empty" }
-%>
<tr class="cbi-section-table-row cbi-rowstyle-<%=rowstyle()%>" id="cbi-<%=self.config%>-<%=section%>">
<%- for i, node in ipairs(self.children) do -%>
<td class="cbi-value-field<% if i ~= (self.headcol or 1) then %> center nowrap<% else %> wrap
<%- end if j > 1 and j == table.getn(self:cfgsections()) then %> bold<% end %>">
<div id="cbi-<%=node.config.."-"..section.."-"..node.option%>">
<%- node:render(section, scope or {}) -%>
</div>
<div id="cbip-<%=node.config.."-"..section.."-"..node.option%>"></div>
</td>
<%- end -%>
</tr>
<%- end -%>
</table>
</div>
</fieldset>

View File

@ -1,20 +0,0 @@
<%+cbi/valueheader%>
<% if not self:cfgvalue(section) or self:cfgvalue(section) ~= "%hidden%" then -%>
<% if self.widget == "select" then %>
<select class="cbi-input-select" onchange="cbi_d_update(this.id)"<%= attr("id", cbid) .. attr("name", cbid) .. ifattr(self.size, "size") .. ifattr(self.style, "style") %>>
<% for i, key in pairs(self.keylist) do -%>
<option id="cbi-<%=self.config.."-"..section.."-"..self.option.."-"..key%>"<%= attr("value", key) .. ifattr(tostring(self:cfgvalue(section) or self.default) == key, "selected", "selected") %>><%=striptags(self.vallist[i])%></option>
<%- end %>
</select>
<% elseif self.widget == "radio" then
local c = 0
for i, key in pairs(self.keylist) do
c = c + 1
%>
<input class="cbi-input-radio" onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)" type="radio"<%= attr("id", cbid..c) .. attr("name", cbid) .. attr("value", key) .. ifattr((self:cfgvalue(section) or self.default) == key, "checked", "checked") %> />
<label<%= attr("for", cbid..c) %>><%=self.vallist[i]%></label>
<% if c == self.size then c = 0 %><% if self.orientation == "horizontal" then %>&#160;<% else %><br /><% end %>
<% end end %>
<% end %>
<%- end %>
<%+cbi/valuefooter%>

View File

@ -1,10 +0,0 @@
<table>
<tr>
<td style="width:130px">
<input type="text" class="cbi-input-text" id="_newrule.name" name="_newrule.name" placeholder="New RSS rule" />
</td>
<td>
<input type="submit" class="cbi-button cbi-button-add" name="_newrule.submit" value="<%:Add%>" />
</td>
</tr>
</table>

View File

@ -1,9 +0,0 @@
<ul class="cbi-tabmenu" style="margin: 9px 0px 0px 0px;">
<%- for i, k in ipairs(self.pages) do -%>
<%- if k.name:lower() ~= "protected" then if self.page:lower() == k.name:lower() then -%>
<li class="cbi-tab"><a href="<%=k.link%>"><%=k.name%></a></li>
<%- else -%>
<li class="cbi-tab-disabled"><a href="<%=k.link%>"><%=k.name%></a></li>
<%- end end -%>
<%- end -%>
</ul>

View File

@ -1,7 +0,0 @@
<style type="text/css">
.cbi-value-error textarea {
border-color: #ff0000;
color: #ff0000;
}
</style>
<%+cbi/tvalue%>

View File

@ -1,161 +0,0 @@
#!/usr/bin/lua
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local uci = require "luci.model.uci".cursor()
local date = require "luci.http.protocol.date"
local bencode = require "bencode"
local xml = require "lxp.lom"
local xmlrpc = require "xmlrpc"
local rtorrent = require "rtorrent"
local nixio = require "nixio"
local common = require "luci.model.cbi.rtorrent.common"
require "luci.model.cbi.rtorrent.string"
CONFIG = "rtorrent"
function log(str)
print(os.date("%Y-%m-%d %H:%M:%S") .. " " .. str)
end
function uci_get_all_sections(config, stype, filter)
local sections = {}
uci:foreach(config, stype, function(s)
if not filter or filter(s) then
table.insert(sections, s)
end
end)
return sections
end
function get_feeds(filter)
return uci_get_all_sections(CONFIG, "rss-feed", filter)
end
function get_rules(filter)
return uci_get_all_sections(CONFIG, "rss-rule", filter)
end
function filter_enabled(s)
return s.enabled == "1"
end
function next_tag(t, tag, i)
if not i then i = 1 end
if t ~= nil then
while t[i] do
if type(t[i]) == "table" and t[i].tag == tag then
return t[i], i
end
i = i + 1
end
end
return nil, i
end
function parse_feed(url)
local ok, content = common.get(url)
if ok and content ~= nil then
local rss, err = xml.parse(content)
if rss ~= nil then
return rss
else
log("Failed to parse rss feed: " .. err)
end
else
log("Failed to download rss feed: " .. url)
end
end
function contains(tbl, value)
for _, v in ipairs(tbl) do
if v == value then
return true
end
end
return false
end
function get_torrent_size(torrent)
local sha1_size = 20
local t, err = bencode.decode(torrent)
if t then
local piece_length = tonumber(t["info"]["piece length"])
local piece_count = t["info"]["pieces"]:len() / sha1_size
return piece_length * piece_count / 1024 / 1024
else
log("Failed to parse torrent file: " .. tostring(torrent))
return nil
end
end
function get_torrent_link(item)
local enclosure = next_tag(item, "enclosure")
if enclosure == nil then
return next_tag(item, "link")[1]
end
return enclosure.attr.url
end
--[[ M A I N ]]--
-- TODO: fix character encoding (eg.: lua expat does not support iso8859-2)
-- string.gsub(rss, "[^\128-\193]", "")
local feed_logfile = uci:get(CONFIG, "logging", "feed_logfile")
if feed_logfile ~= nil then
feed_log = assert(io.open(feed_logfile, "a"))
end
local rules = get_rules(filter_enabled)
for _, feed in ipairs(get_feeds(filter_enabled)) do
log("Processing \"" .. feed.name .. "\" rss feed")
local rss = parse_feed(feed.url)
local channel = next_tag(rss, "channel")
local item, i = next_tag(channel, "item")
local lastupdate = tonumber(feed.lastupdate) or 0
if item then
uci:set(CONFIG, feed[".name"], "lastupdate", date.to_unix(next_tag(item, "pubDate")[1]))
uci:save(CONFIG)
uci:commit(CONFIG)
end
while item do
local pubdate = date.to_unix(next_tag(item, "pubDate")[1])
if pubdate > lastupdate then
local title = next_tag(item, "title")[1]
if feed_log ~= nil then
feed_log:write(os.date("!%Y-%m-%d %H:%M:%S", pubdate) ..
" (" .. feed.name .. ") " .. title .. "\n")
end
for _, rule in ipairs(rules) do
if rule.feed and contains(rule.feed:split(";"), feed.name)
and title:lower():find(rule.match:lower())
and (not rule.exclude or not title:lower():find(rule.exclude:lower())) then
local link = get_torrent_link(item)
local ok, torrent = common.get(link)
local size = get_torrent_size(torrent)
if ok and size
and (not rule.minsize or (size >= tonumber(rule.minsize)))
and (not rule.maxsize or (size <= tonumber(rule.maxsize))) then
log("Matched rss rule: " .. rule.name .. " (" .. link .. ")")
local params = {}
table.insert(params, rule.autostart == "1" and "load.raw_start" or "load.raw")
table.insert(params, "") -- target
table.insert(params, xmlrpc.newTypedValue((nixio.bin.b64encode(torrent)), "base64"))
table.insert(params, "d.directory.set=\"" .. rule.destdir .. "\"")
if rule.tags then table.insert(params, "d.custom1.set=\"" .. rule.tags .. "\"") end
table.insert(params, "d.custom3.set=" .. nixio.bin.b64encode(link))
rtorrent.call(unpack(params))
end
end
end
end
item, i = next_tag(channel, "item", i + 1)
end
end
if feed_log ~= nil then
feed_log:flush()
feed_log:close()
end

View File

@ -1,103 +0,0 @@
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- Licensed to the public under the GNU General Public License.
local ipairs, string, tostring, tonumber, table = ipairs, string, tostring, tonumber, table
local assert, type, unpack = assert, type, unpack
local nixio = require "nixio"
local socket = require "socket"
local xmlrpc = require "xmlrpc"
local scgi = require "xmlrpc.scgi"
local SCGI_ADDRESS = "localhost"
local SCGI_PORT = 5000
local HTTP_AUTH_USER = "rtorrent"
local HTTP_AUTH_PASSWORD = "czNz9JwdcLYcGDcVnZbQ"
module "rtorrent"
function map(array, func)
local new_array = {}
for i, v in ipairs(array) do
new_array[i] = func(v)
end
return new_array
end
function alter(prefix, methods, postfix)
methods = map(methods, function(method)
if method == 0 then return method end
if prefix then method = prefix .. method end
if postfix then method = method .. postfix end
return method
end)
return methods
end
function format(method_type, res, methods)
local formatted = {}
for _, r in ipairs(res) do
local item = {}
for i, v in ipairs(r) do
item[methods[method_type == "d." and i or i + 1]:gsub("%.", "_")] = v
end
table.insert(formatted, item)
end
return formatted
end
function call(method, ...)
local ok, res = scgi.call(SCGI_ADDRESS, SCGI_PORT, method, ...)
if not ok and res == "socket connect failed" then
assert(ok, "\n\nFailed to connect to rtorrent: rpc port not reachable!\n"
.. "Possible reasons:\n"
.. "- not the rpc version of rtorrent is installed\n"
.. "- scgi port is not defined in .rtorrent.rc (scgi_port = 127.0.0.1:5000)\n"
.. "- rtorrent is not running (ps | grep rtorrent)\n")
end
assert(ok, string.format("XML-RPC call failed on client: %s", tostring(res)))
return res
end
function rpc(xml)
local auth = "Basic " .. nixio.bin.b64encode(HTTP_AUTH_USER .. ":" .. HTTP_AUTH_PASSWORD)
if auth ~= nixio.getenv("HTTP_AUTHORIZATION") then
return 'Status: 401 Unauthorized\r\n'
.. 'WWW-Authenticate: Basic realm="rTorrent"\r\n\r\n'
end
local sock = socket.connect(SCGI_ADDRESS, SCGI_PORT)
if sock ~= nil then
sock:send(scgi.netstring(xml))
local err, code, headers, body = scgi.receive(sock)
if tonumber(code) == 200 then
return 'Status: 200 OK\r\n'
.. 'Content-Type: application/xml\r\n\r\n' .. body
end
end
return 'Status: 500 Internal Server Error\r\n\r\n'
end
function multicall(method_type, filter, ...)
local res = (method_type == "d.")
and call(method_type .. "multicall2", "", filter, unpack(alter(method_type, {...}, "=")))
or call(method_type .. "multicall", filter, unpack(alter(method_type, {...}, "=")))
return format(method_type, res, {...})
end
function batchcall(methods, params, prefix, postfix)
local p = type(params) == "table" and params or { params }
local methods_array = {}
for _, m in ipairs(alter(prefix, methods, postfix)) do
table.insert(methods_array, {
["methodName"] = m,
["params"] = xmlrpc.newTypedValue(p, "array")
})
end
local res = {}
for i, r in ipairs(call("system.multicall", xmlrpc.newTypedValue(methods_array, "array"))) do
res[methods[i]] = r[1]
end
return res
end

View File

@ -1,608 +0,0 @@
-- Copyright 2003-2010 Kepler Project
-- XML-RPC implementation for Lua.
local lxp = require "lxp"
local lom = require "lxp.lom"
local assert, error, ipairs, pairs, select, type, tonumber, unpack = assert, error, ipairs, pairs, select, type, tonumber, unpack
local format, gsub, strfind, strsub = string.format, string.gsub, string.find, string.sub
local concat, tinsert = table.concat, table.insert
local ceil = math.ceil
local parse = lom.parse
module (...)
_COPYRIGHT = "Copyright (C) 2003-2014 Kepler Project"
_DESCRIPTION = "LuaXMLRPC is a library to make remote procedure calls using XML-RPC"
_PKGNAME = "LuaXMLRPC"
_VERSION_MAJOR = 1
_VERSION_MINOR = 2
_VERSION_MICRO = 2
_VERSION = _VERSION_MAJOR .. "." .. _VERSION_MINOR .. "." .. _VERSION_MICRO
---------------------------------------------------------------------
-- XML-RPC Parser
---------------------------------------------------------------------
---------------------------------------------------------------------
local function trim (s)
return (type(s) == "string" and gsub (s, "^%s*(.-)%s*$", "%1"))
end
---------------------------------------------------------------------
local function is_space (s)
return type(s) == "string" and trim(s) == ""
end
---------------------------------------------------------------------
-- Get next non-space element from tab starting from index i.
-- @param tab Table.
-- @param i Numeric index.
-- @return Object and its position on table; nil and an invalid index
-- when there is no more elements.
---------------------------------------------------------------------
function next_nonspace (tab, i)
if not i then i = 1 end
while is_space (tab[i]) do i = i+1 end
return tab[i], i
end
---------------------------------------------------------------------
-- Get next element of tab with the given tag starting from index i.
-- @param tab Table.
-- @param tag String with the name of the tag.
-- @param i Numeric index.
-- @return Object and its position on table; nil and an invalid index
-- when there is no more elements.
---------------------------------------------------------------------
local function next_tag (tab, tag, i)
if not i then i = 1 end
while tab[i] do
if type (tab[i]) == "table" and tab[i].tag == tag then
return tab[i], i
end
i = i + 1
end
return nil, i
end
---------------------------------------------------------------------
local function x2number (tab)
if tab.tag == "int" or tab.tag == "i4" or tab.tag == "i8" or tab.tag == "double" then
return tonumber (next_nonspace (tab, 1), 10)
end
end
---------------------------------------------------------------------
local function x2boolean (tab)
if tab.tag == "boolean" then
local v = next_nonspace (tab, 1)
return v == true or v == "true" or tonumber (v) == 1 or false
end
end
---------------------------------------------------------------------
local function x2string (tab)
return tab.tag == "string" and (tab[1] or "")
end
---------------------------------------------------------------------
local function x2date (tab)
return tab.tag == "dateTime.iso8601" and next_nonspace (tab, 1)
end
---------------------------------------------------------------------
local function x2base64 (tab)
return tab.tag == "base64" and next_nonspace (tab, 1)
end
---------------------------------------------------------------------
local function x2name (tab)
return tab.tag == "name" and next_nonspace (tab, 1)
end
local x2value
---------------------------------------------------------------------
-- Disassemble a member object in its name and value parts.
-- @param tab Table with a DOM representation.
-- @return String (name) and Object (value).
-- @see x2name, x2value.
---------------------------------------------------------------------
local function x2member (tab)
return
x2name (next_tag(tab,"name")),
x2value (next_tag(tab,"value"))
end
---------------------------------------------------------------------
-- Disassemble a struct object into a Lua table.
-- @param tab Table with DOM representation.
-- @return Table with "name = value" pairs.
---------------------------------------------------------------------
local function x2struct (tab)
if tab.tag == "struct" then
local res = {}
for i = 1, #tab do
if not is_space (tab[i]) then
local name, val = x2member (tab[i])
res[name] = val
end
end
return res
end
end
---------------------------------------------------------------------
-- Disassemble an array object into a Lua table.
-- @param tab Table with DOM representation.
-- @return Table.
---------------------------------------------------------------------
local function x2array (tab)
if tab.tag == "array" then
local d = next_tag (tab, "data")
local res = {}
for i = 1, #d do
if not is_space (d[i]) then
tinsert (res, x2value (d[i]))
end
end
return res
end
end
---------------------------------------------------------------------
local xmlrpc_types = {
int = x2number,
i4 = x2number,
i8 = x2number,
boolean = x2boolean,
string = x2string,
double = x2number,
["dateTime.iso8601"] = x2date,
base64 = x2base64,
struct = x2struct,
array = x2array,
}
local x2param, x2fault
---------------------------------------------------------------------
-- Disassemble a methodResponse into a Lua object.
-- @param tab Table with DOM representation.
-- @return Boolean (indicating wether the response was successful)
-- and (a Lua object representing the return values OR the fault
-- string and the fault code).
---------------------------------------------------------------------
local function x2methodResponse (tab)
assert (type(tab) == "table", "Not a table")
assert (tab.tag == "methodResponse",
"Not a `methodResponse' tag: "..tab.tag)
local t = next_nonspace (tab, 1)
if t.tag == "params" then
return true, unpack (x2param (t))
elseif t.tag == "fault" then
local f = x2fault (t)
return false, f.faultString, f.faultCode
else
error ("Couldn't find a <params> nor a <fault> element")
end
end
---------------------------------------------------------------------
-- Disassemble a value element into a Lua object.
-- @param tab Table with DOM representation.
-- @return Object.
---------------------------------------------------------------------
x2value = function (tab)
local t = tab.tag
assert (t == "value", "Not a `value' tag: "..t)
local n = next_nonspace (tab)
if type(n) == "string" or type(n) == "number" then
return n
elseif type (n) == "table" then
local t = n.tag
local get = xmlrpc_types[t]
if not get then error ("Invalid <"..t.."> element") end
return get (next_nonspace (tab))
elseif type(n) == "nil" then
-- the next best thing is to assume it's an empty string
return ""
end
end
---------------------------------------------------------------------
-- Disassemble a fault element into a Lua object.
-- @param tab Table with DOM representation.
-- @return Object.
---------------------------------------------------------------------
x2fault = function (tab)
assert (tab.tag == "fault", "Not a `fault' tag: "..tab.tag)
return x2value (next_nonspace (tab))
end
---------------------------------------------------------------------
-- Disassemble a param element into a Lua object.
-- Ignore white spaces between elements.
-- @param tab Table with DOM representation.
-- @return Object.
---------------------------------------------------------------------
x2param = function (tab)
assert (tab.tag == "params", "Not a `params' tag")
local res = {}
local p, i = next_nonspace (tab, 1)
while p do
if p.tag == "param" then
tinsert (res, x2value (next_tag (p, "value")))
end
p, i = next_nonspace (tab, i+1)
end
return res
end
---------------------------------------------------------------------
-- Disassemble a methodName element into a Lua object.
-- @param tab Table with DOM representation.
-- @return Object.
---------------------------------------------------------------------
local function x2methodName (tab)
assert (tab.tag == "methodName", "Not a `methodName' tag: "..tab.tag)
return (next_nonspace (tab, 1))
end
---------------------------------------------------------------------
-- Disassemble a methodCall element into its name and a list of parameters.
-- @param tab Table with DOM representation.
-- @return Object.
---------------------------------------------------------------------
local function x2methodCall (tab)
assert (tab.tag == "methodCall", "Not a `methodCall' tag: "..tab.tag)
return
x2methodName (next_tag (tab,"methodName")),
x2param (next_tag (tab,"params"))
end
---------------------------------------------------------------------
-- End of XML-RPC Parser
---------------------------------------------------------------------
---------------------------------------------------------------------
-- Convert a Lua Object into an XML-RPC string.
---------------------------------------------------------------------
---------------------------------------------------------------------
local formats = {
boolean = "<boolean>%d</boolean>",
number = "<double>%d</double>",
string = "<string>%s</string>",
base64 = "<base64>%s</base64>",
array = "<array><data>\n%s\n</data></array>",
double = "<double>%s</double>",
int = "<int>%s</int>",
struct = "<struct>%s</struct>",
member = "<member><name>%s</name>%s</member>",
value = "<value>%s</value>",
param = "<param>%s</param>",
params = [[
<params>
%s
</params>]],
fault = [[
<fault>
%s
</fault>]],
methodCall = [[
<?xml version="1.0"?>
<methodCall>
<methodName>%s</methodName>
%s
</methodCall>
]],
methodResponse = [[
<?xml version="1.0"?>
<methodResponse>
%s
</methodResponse>]],
}
formats.table = formats.struct
local toxml = {}
toxml.double = function (v,t) return format (formats.double, v) end
toxml.int = function (v,t) return format (formats.int, v) end
toxml.string = function (v,t) return format (formats.string, v) end
toxml.base64 = function (v,t) return format (formats.base64, v) end
---------------------------------------------------------------------
-- Build a XML-RPC representation of a boolean.
-- @param v Object.
-- @return String.
---------------------------------------------------------------------
function toxml.boolean (v)
local n = (v and 1) or 0
return format (formats.boolean, n)
end
---------------------------------------------------------------------
-- Build a XML-RPC representation of a number.
-- @param v Object.
-- @param t Object representing the XML-RPC type of the value.
-- @return String.
---------------------------------------------------------------------
function toxml.number (v, t)
local tt = (type(t) == "table") and t["*type"]
if tt == "int" or tt == "i4" or tt == "i8" then
return toxml.int (v, t)
elseif tt == "double" then
return toxml.double (v, t)
elseif v == ceil(v) then
return toxml.int (v, t)
else
return toxml.double (v, t)
end
end
---------------------------------------------------------------------
-- @param typ Object representing a type.
-- @return Function that generate an XML element of the given type.
-- The object could be a string (as usual in Lua) or a table with
-- a field named "type" that should be a string with the XML-RPC
-- type name.
---------------------------------------------------------------------
local function format_func (typ)
if type (typ) == "table" then
return toxml[typ.type]
else
return toxml[typ]
end
end
---------------------------------------------------------------------
-- @param val Object representing an array of values.
-- @param typ Object representing the type of the value.
-- @return String representing the equivalent XML-RPC value.
---------------------------------------------------------------------
function toxml.array (val, typ)
local ret = {}
local et = typ.elemtype
local f = format_func (et)
for i,v in ipairs (val) do
if et and et ~= "array" then
tinsert (ret, format (formats.value, f (v, et)))
else
local ct,cv = type_val(v)
local cf = format_func(ct)
tinsert (ret, format (formats.value, cf(cv, ct)))
end
end
return format (formats.array, concat (ret, '\n'))
end
---------------------------------------------------------------------
---------------------------------------------------------------------
function toxml.struct (val, typ)
local ret = {}
if type (typ) == "table" then
for n,t in pairs (typ.elemtype) do
local f = format_func (t)
tinsert (ret, format (formats.member, n, f (val[n], t)))
end
else
for i, v in pairs (val) do
tinsert (ret, toxml.member (i, v))
end
end
return format (formats.struct, concat (ret))
end
toxml.table = toxml.struct
---------------------------------------------------------------------
---------------------------------------------------------------------
function toxml.member (n, v)
return format (formats.member, n, toxml.value (v))
end
---------------------------------------------------------------------
-- Get type and value of object.
---------------------------------------------------------------------
function type_val (obj)
local t = type (obj)
local v = obj
if t == "table" then
t = obj["*type"] or "table"
v = obj["*value"] or obj
end
return t, v
end
---------------------------------------------------------------------
-- Convert a Lua object to a XML-RPC object (plain string).
---------------------------------------------------------------------
function toxml.value (obj)
local to, val = type_val (obj)
if type(to) == "table" then
return format (formats.value, toxml[to.type] (val, to))
else
-- primitive (not structured) types.
--return format (formats[to], val)
return format (formats.value, toxml[to] (val, to))
end
end
---------------------------------------------------------------------
-- @param ... List of parameters.
-- @return String representing the `params' XML-RPC element.
---------------------------------------------------------------------
function toxml.params (...)
local params_list = {}
for i = 1, select ("#", ...) do
params_list[i] = format (formats.param, toxml.value (select (i, ...)))
end
return format (formats.params, concat (params_list, '\n '))
end
---------------------------------------------------------------------
-- @param method String with method's name.
-- @param ... List of parameters.
-- @return String representing the `methodCall' XML-RPC element.
---------------------------------------------------------------------
function toxml.methodCall (method, ...)
local idx = strfind (method, "[^A-Za-z_.:/0-9]")
if idx then
error (format ("Invalid character `%s'", strsub (method, idx, idx)))
end
return format (formats.methodCall, method, toxml.params (...))
end
---------------------------------------------------------------------
-- @param err String with error message.
-- @return String representing the `fault' XML-RPC element.
---------------------------------------------------------------------
function toxml.fault (err)
local code
local message = err
if type (err) == "table" then
code = err.code
message = err.message
end
return format (formats.fault, toxml.value {
faultCode = { ["*type"] = "int", ["*value"] = code or err.faultCode or 1 },
faultString = message or err.faultString or "fatal error",
})
end
---------------------------------------------------------------------
-- @param ok Boolean indicating if the response was correct or a
-- fault one.
-- @param params Object containing the response contents.
-- @return String representing the `methodResponse' XML-RPC element.
---------------------------------------------------------------------
function toxml.methodResponse (ok, params)
local resp
if ok then
resp = toxml.params (params)
else
resp = toxml.fault (params)
end
return format (formats.methodResponse, resp)
end
---------------------------------------------------------------------
-- End of converter from Lua to XML-RPC.
---------------------------------------------------------------------
---------------------------------------------------------------------
-- Create a representation of an array with the given element type.
---------------------------------------------------------------------
function newArray (elemtype)
return { type = "array", elemtype = elemtype, }
end
---------------------------------------------------------------------
-- Create a representation of a structure with the given members.
---------------------------------------------------------------------
function newStruct (members)
return { type = "struct", elemtype = members, }
end
---------------------------------------------------------------------
-- Create a representation of a value according to a type.
-- @param val Any Lua value.
-- @param typ A XML-RPC type.
---------------------------------------------------------------------
function newTypedValue (val, typ)
return { ["*type"] = typ, ["*value"] = val }
end
---------------------------------------------------------------------
-- Create the XML-RPC string used to call a method.
-- @param method String with method name.
-- @param ... Parameters to the call.
-- @return String with the XML string/document.
---------------------------------------------------------------------
function clEncode (method, ...)
return toxml.methodCall (method, ...)
end
---------------------------------------------------------------------
-- Convert the method response document to a Lua table.
-- @param meth_resp String with XML document.
-- @return Boolean indicating whether the call was successful or not;
-- and a Lua object with the converted response element.
---------------------------------------------------------------------
function clDecode (meth_resp)
local d = parse (meth_resp)
if type(d) ~= "table" then
error ("Not an XML document: "..meth_resp)
end
return x2methodResponse (d)
end
---------------------------------------------------------------------
-- Convert the method call (client request) document to a name and
-- a list of parameters.
-- @param request String with XML document.
-- @return String with method's name AND the table of arguments.
---------------------------------------------------------------------
function srvDecode (request)
local d = parse (request)
if type(d) ~= "table" then
error ("Not an XML document: "..request)
end
return x2methodCall (d)
end
---------------------------------------------------------------------
-- Convert a table into an XML-RPC methodReponse element.
-- @param obj Lua object.
-- @param is_fault Boolean indicating wether the result should be
-- a `fault' element (default = false).
-- @return String with XML-RPC response.
---------------------------------------------------------------------
function srvEncode (obj, is_fault)
local ok = not (is_fault or false)
return toxml.methodResponse (ok, obj)
end
---------------------------------------------------------------------
-- Register the methods.
-- @param tab_or_func Table or mapping function.
-- If a table is given, it can have one level of objects and then the
-- methods;
-- if a function is given, it will be used as the dispatcher.
-- The given function should return a Lua function that implements.
---------------------------------------------------------------------
dispatch = error
function srvMethods (tab_or_func)
local t = type (tab_or_func)
if t == "function" then
dispatch = tab_or_func
elseif t == "table" then
dispatch = function (name)
local ok, _, obj, method = strfind (name, "^([^.]+)%.(.+)$")
if not ok then
return tab_or_func[name]
else
if tab_or_func[obj] and tab_or_func[obj][method] then
return function (...)
return tab_or_func[obj][method] (obj, ...)
end
else
return nil
end
end
end
else
error ("Argument is neither a table nor a function")
end
end

View File

@ -1,76 +0,0 @@
-- Copyright 2003-2010 Kepler Project
-- Copyright 2014-2018 Sandor Balazsi <sandor.balazsi@gmail.com>
-- XML-RPC over SCGI.
local error, tonumber, tostring, unpack = error, tonumber, tostring, unpack
local socket= require"socket"
local string= require"string"
local xmlrpc= require"xmlrpc"
module("xmlrpc.scgi")
---------------------------------------------------------------------
-- Call a remote method.
-- @param addr String with the address of the SCGI server.
-- @param port The port of the SCGI server.
-- @param method String with the name of the method to be called.
-- @return Table with the response(could be a `fault' or a `params'
-- XML-RPC element).
---------------------------------------------------------------------
function call(addr, port, method, ...)
local request_body = xmlrpc.clEncode(method, ...)
local sock = socket.connect(addr, port)
if sock == nil then
return false, "socket connect failed"
end
sock:send(netstring(request_body))
local err, code, headers, body = receive(sock)
if tonumber(code) == 200 then
return xmlrpc.clDecode(body)
else
error(tostring(err or code))
end
end
---------------------------------------------------------------------
-- Encode message as netstring
-- @param request_body String with the message
-- @return String with the encoded message
---------------------------------------------------------------------
function netstring(request_body)
local null = "\0"
local content_length = "CONTENT_LENGTH" .. null .. string.len(request_body) .. null
local scgi_enable = "SCGI" .. null .. "1" .. null
local request_method = "REQUEST_METHOD" .. null .. "POST" .. null
local server_protocol = "SERVER_PROTOCOL" .. null .. "HTTP/1.1" .. null
local header = content_length .. scgi_enable .. request_method .. server_protocol
return string.len(header) .. ":" .. header .. "," .. request_body
end
---------------------------------------------------------------------
-- Receive and parse socket response
-- @param sock Socket instance
-- @return Headers, body and error codes
---------------------------------------------------------------------
function receive(sock)
local line, body, err
local headers = {}
line, err = sock:receive()
if err then return err, "500" end
while line ~= "" do
local name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
if not(name and value) then return "malformed reponse header: " .. line, "500" end
headers[string.lower(name)] = value
line, err = sock:receive()
if err then return err, "500" end
end
body = sock:receive(headers["content-length"])
local code = socket.skip(2, string.find(headers["status"], "^(%d%d%d)"))
return err, code, headers, body
end

View File

@ -1,4 +0,0 @@
#!/usr/bin/lua
local rtorrent = require "rtorrent"
io.write(rtorrent.rpc(io.read("*all")))
io.flush()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 B

Some files were not shown because too many files have changed in this diff Show More