pull/109/head
midoks 6 years ago
parent 35d75781f0
commit 801478ba30
  1. 1
      .gitignore
  2. 21
      plugins/qbittorrent/LICENSE
  3. 14
      plugins/qbittorrent/README.md
  4. 35
      plugins/qbittorrent/conf/qb.conf
  5. 34
      plugins/qbittorrent/conf/qb.sql
  6. BIN
      plugins/qbittorrent/ico.png
  7. 23
      plugins/qbittorrent/index.html
  8. 357
      plugins/qbittorrent/index.py
  9. 18
      plugins/qbittorrent/info.json
  10. 46
      plugins/qbittorrent/init.d/qbittorrent.tpl
  11. 74
      plugins/qbittorrent/install.sh
  12. 186
      plugins/qbittorrent/js/qbittorrent.js
  13. BIN
      plugins/qbittorrent/screenshot/ss1.jpg
  14. 719
      plugins/qbittorrent/workers/qbittorrent_worker.py
  15. 5
      plugins/qbittorrent/workers/rsync.sh

1
.gitignore vendored

@ -121,6 +121,5 @@ data/close.pl
ssl/input.pl
data/datadir.pl
data/502Task.pl
plugins/qbittorrent/
data/default.pl
data/backup.pl

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Mr Chen
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.

@ -0,0 +1,14 @@
# mw-qbittorrent
mdserver-web|qbittorrent管理
### 安装过程
```
* 先进行压缩 `cd mw-qbittorrent && zip qbittorrent.zip -r ./* `
* 在mdserver-web点击`添加插件`
```
### 截图
[![截图1](/screenshot/ss1.jpg)](/screenshot/ss1.jpg)

@ -0,0 +1,35 @@
[db]
DB_HOST = 127.0.0.1
DB_PORT = 3306
DB_USER = qbittorrent
DB_PASS = qbittorrent
DB_NAME = qbittorrent
[qb]
QB_HOST = 127.0.0.1
QB_PORT = 8080
QB_USER = admin
QB_PWD = adminadmin
[file]
FILE_TO={$SERVER_PATH}/tmp
FILE_TRANSFER_TO={$SERVER_PATH}/tmp
FILE_OWN=www
FILE_GROUP=www
FILE_ENC_SWITCH=0
FILE_API_URL=http://v.demo.com/api/key/id/{$KEY}
FILE_ASYNC_SWITCH=0
[task]
TASK_SIZE_LIMIT=1
TASK_RATE=4
TASK_COMPLETED_RATE=10
TASK_DEBUG=0
[setting]
QUEUE_SWITCH = 1
MAX_ACTIVE_UPLOADS = 1
MAX_ACTIVE_TORRENTS = 10
MAX_ACTIVE_DOWNLOADS = 10

@ -0,0 +1,34 @@
CREATE TABLE `pl_hash_list` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` text NOT NULL,
`info_hash` varchar(40) NOT NULL,
`length` bigint(20) NOT NULL,
`status` char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `info_hash` (`info_hash`),
KEY `create_time` (`create_time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `pl_hash_file` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`pid` bigint(20) NOT NULL,
`name` text NOT NULL,
`m3u8` varchar(40) NOT NULL,
`key` char(40) NULL DEFAULT '',
`length` bigint(20) NULL DEFAULT 0,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `create_time` (`create_time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `pl_hash_queue` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`info_hash` char(40) NOT NULL,
`length` bigint(20) NOT NULL DEFAULT 0,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
INDEX `length`(`length`) USING BTREE,
KEY `created_at` (`created_at`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@ -0,0 +1,23 @@
<div class="bt-form">
<div class="bt-w-main">
<div class="bt-w-menu">
<p class="bgw" onclick="pluginService('qbittorrent');">服务</p>
<p onclick="pluginInitD('qbittorrent');">自启动</p>
<p onclick="pluginConfig('qbittorrent', '','get_sql');" title="手动导入SQL">导入SQL</p>
<p onclick="pluginConfig('qbittorrent');">配置</p>
<p onclick="pluginConfig('qbittorrent', '','rsync_shell');" title="异步脚本">异步脚本</p>
<p onclick="pluginLogs('qbittorrent','','get_run_Log', 25);">日志</p>
<p onclick="qbList();">列表</p>
</div>
<div class="bt-w-con pd15">
<div class="soft-man-con">
</div>
</div>
</div>
</div>
<script type="text/javascript">
pluginService('qbittorrent');
$.getScript( "/plugins/file?name=qbittorrent&f=js/qbittorrent.js");
</script>

@ -0,0 +1,357 @@
# coding: utf-8
import time
import random
import os
import json
import re
import sys
sys.path.append(os.getcwd() + "/class/core")
import public
reload(sys)
sys.setdefaultencoding('utf8')
sys.path.append('/usr/local/lib/python2.7/site-packages')
app_debug = False
if public.isAppleSystem():
app_debug = True
def getPluginName():
return 'qbittorrent'
def getPluginDir():
return public.getPluginDir() + '/' + getPluginName()
def getServerDir():
return public.getServerDir() + '/' + getPluginName()
def getInitDFile():
if app_debug:
return '/tmp/' + getPluginName()
return '/etc/init.d/' + getPluginName()
def getArgs():
args = sys.argv[2:]
tmp = {}
args_len = len(args)
if args_len == 1:
t = args[0].strip('{').strip('}')
t = t.split(':')
tmp[t[0]] = t[1]
elif args_len > 1:
for i in range(len(args)):
t = args[i].split(':')
tmp[t[0]] = t[1]
return tmp
def checkArgs(data, ck=[]):
for i in range(len(ck)):
if not ck[i] in data:
return (False, public.returnJson(False, '参数:(' + ck[i] + ')没有!'))
return (True, public.returnJson(True, 'ok'))
def getInitDTpl():
path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl"
return path
def getSqlFile():
file = getPluginDir() + "/conf/qb.sql"
return file
def getRsyncShell():
file = getServerDir() + "/workers/rsync.sh"
return file
def getConf():
file = getServerDir() + "/qb.conf"
return file
def getRunLog():
file = getServerDir() + "/logs.pl"
return file
def contentReplace(content):
service_path = public.getServerDir()
content = content.replace('{$SERVER_PATH}', service_path)
return content
def initDreplace():
ddir = getServerDir() + '/workers'
if not os.path.exists(ddir):
sdir = getPluginDir() + '/workers'
public.execShell('cp -rf ' + sdir + ' ' + getServerDir())
cfg = getServerDir() + '/qb.conf'
if not os.path.exists(cfg):
cfg_tpl = getPluginDir() + '/conf/qb.conf'
content = public.readFile(cfg_tpl)
content = contentReplace(content)
public.writeFile(cfg, content)
file_tpl = getInitDTpl()
service_path = os.path.dirname(os.getcwd())
initD_path = getServerDir() + '/init.d'
if not os.path.exists(initD_path):
os.mkdir(initD_path)
file_bin = initD_path + '/' + getPluginName()
# initd replace
if not os.path.exists(file_bin):
content = public.readFile(file_tpl)
content = contentReplace(content)
public.writeFile(file_bin, content)
public.execShell('chmod +x ' + file_bin)
return file_bin
def status():
data = public.execShell(
"ps -ef|grep qbittorrent_worker | grep -v grep | awk '{print $2}'")
if data[0] == '':
return 'stop'
return 'start'
def start():
cmd = "ps -ef | grep qbittorrent-nox |grep -v grep |awk '{print $2}'"
ret = public.execShell(cmd)
if ret[0] == '':
public.execShell('qbittorrent-nox -d')
file = initDreplace()
data = public.execShell(file + ' start')
if data[1] == '':
return 'ok'
return data[1]
def stop():
file = initDreplace()
data = public.execShell(file + ' stop')
# cmd = "ps -ef | grep qbittorrent-nox |grep -v grep |awk '{print $2}' | xargs kill"
# public.execShell(cmd)
if data[1] == '':
return 'ok'
return data[1]
def restart():
file = initDreplace()
data = public.execShell(file + ' restart')
if data[1] == '':
return 'ok'
return data[1]
def reload():
file = initDreplace()
data = public.execShell(file + ' reload')
if data[1] == '':
return 'ok'
return data[1]
def initdStatus():
if not app_debug:
if public.isAppleSystem():
return "Apple Computer does not support"
initd_bin = getInitDFile()
if os.path.exists(initd_bin):
return 'ok'
return 'fail'
def initdInstall():
import shutil
if not app_debug:
if public.isAppleSystem():
return "Apple Computer does not support"
mysql_bin = initDreplace()
initd_bin = getInitDFile()
shutil.copyfile(mysql_bin, initd_bin)
public.execShell('chmod +x ' + initd_bin)
public.execShell('chkconfig --add ' + getPluginName())
return 'ok'
def initdUinstall():
if not app_debug:
if public.isAppleSystem():
return "Apple Computer does not support"
initd_bin = getInitDFile()
os.remove(initd_bin)
public.execShell('chkconfig --del ' + getPluginName())
return 'ok'
def matchData(reg, content):
tmp = re.search(reg, content).groups()
return tmp[0]
def getDbConfInfo():
cfg = getConf()
content = public.readFile(cfg)
data = {}
data['DB_HOST'] = matchData("DB_HOST\s*=\s(.*)", content)
data['DB_USER'] = matchData("DB_USER\s*=\s(.*)", content)
data['DB_PORT'] = matchData("DB_PORT\s*=\s(.*)", content)
data['DB_PASS'] = matchData("DB_PASS\s*=\s(.*)", content)
data['DB_NAME'] = matchData("DB_NAME\s*=\s(.*)", content)
return data
def getQbConf():
cfg = getConf()
content = public.readFile(cfg)
data = {}
data['QB_HOST'] = matchData("QB_HOST\s*=\s(.*)", content)
data['QB_PORT'] = matchData("QB_PORT\s*=\s(.*)", content)
data['QB_USER'] = matchData("QB_USER\s*=\s(.*)", content)
data['QB_PWD'] = matchData("QB_PWD\s*=\s(.*)", content)
return data
def pMysqlDb():
data = getDbConfInfo()
conn = mysql.mysql()
conn.setHost(data['DB_HOST'])
conn.setUser(data['DB_USER'])
conn.setPwd(data['DB_PASS'])
conn.setPort(int(data['DB_PORT']))
conn.setDb(data['DB_NAME'])
return conn
def pQbClient():
from qbittorrent import Client
info = getQbConf()
url = 'http://' + info['QB_HOST'] + ':' + info['QB_PORT'] + '/'
qb = Client(url)
qb.login(info['QB_USER'], info['QB_PWD'])
return qb
def getQbUrl():
info = getQbConf()
url = 'http://' + info['QB_HOST'] + ':' + info['QB_PORT'] + '/'
return public.returnJson(True, 'ok', url)
def qbList():
args = getArgs()
# data = checkArgs(args, ['type'])
# if not data[0]:
# return data[1]
args_type = ''
if 'type' in args:
args_type = args['type']
f = ['downloading', 'completed']
tfilter = ''
if args_type in f:
tfilter = args['type']
try:
qb = pQbClient()
torrents = qb.torrents(filter=tfilter)
data = {}
data['type'] = tfilter
data['torrents'] = torrents
return public.returnJson(True, 'ok', data)
except Exception as e:
return public.returnJson(False, str(e))
def qbDel():
args = getArgs()
data = checkArgs(args, ['hash'])
if not data[0]:
return data[1]
qb = pQbClient()
data = qb.delete(args['hash'])
return public.returnJson(True, '操作成功!', data)
def qbAdd():
args = getArgs()
data = checkArgs(args, ['hash'])
if not data[0]:
return data[1]
url = 'magnet:?xt=urn:btih:' + args['hash']
qb = pQbClient()
data = qb.download_from_link(url)
return public.returnJson(True, '操作成功!', data)
def test():
qb = pQbClient()
# magnet_link = "magnet:?xt=urn:btih:57a0ec92a61c60585f1b7a206a75798aa69285a5"
# print qb.download_from_link(magnet_link)
torrents = qb.torrents(filter='downloading')
for torrent in torrents:
print public.returnJson(False, torrent)
if __name__ == "__main__":
func = sys.argv[1]
if func == 'status':
print status()
elif func == 'start':
print start()
elif func == 'stop':
print stop()
elif func == 'restart':
print restart()
elif func == 'reload':
print reload()
elif func == 'initd_status':
print initdStatus()
elif func == 'initd_install':
print initdInstall()
elif func == 'initd_uninstall':
print initdUinstall()
elif func == 'get_sql':
print getSqlFile()
elif func == 'rsync_shell':
print getRsyncShell()
elif func == 'conf':
print getConf()
elif func == 'get_run_Log':
print getRunLog()
elif func == 'qb_list':
print qbList()
elif func == 'qb_del':
print qbDel()
elif func == 'qb_add':
print qbAdd()
elif func == 'qb_url':
print getQbUrl()
elif func == 'test':
print test()
else:
print 'error'

@ -0,0 +1,18 @@
{
"sort": 7,
"ps": "一个新的轻量级BitTorrent客户端",
"name": "qbittorrent",
"title": "qBittorrent",
"shell": "install.sh",
"versions":["4.1.5"],
"updates":["4.1.5"],
"tip": "soft",
"checks": "server/qbittorrent",
"path": "server/qbittorrent",
"display": 1,
"author": "Zend",
"date": "2017-04-01",
"home": "https://www.qbittorrent.org",
"type": 0,
"pid": "5"
}

@ -0,0 +1,46 @@
#!/bin/sh
# chkconfig: 2345 55 25
# description: qbittorrent Service
### BEGIN INIT INFO
# Provides: qbittorrent
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts qbittorrent
# Description: starts the MDW-Web
### END INIT INFO
qb_start(){
cd {$SERVER_PATH}/qbittorrent/workers
nohup python qbittorrent_worker.py > {$SERVER_PATH}/qbittorrent/logs.pl 2>&1 &
echo "qbittorrent started"
}
qb_stop(){
echo "Stopping ..."
#ps -ef | grep qbittorrent-nox-bin | grep -v grep | awk '{print $2}' | xargs kill -9
ps -ef | grep "qbittorrent_worker.py" | grep -v grep | awk '{print $2}' | xargs kill -9
echo "qbittorrent stopped"
}
case "$1" in
start)
qb_start
;;
stop)
qb_stop
;;
restart|reload)
qb_stop
sleep 0.3
qb_start
;;
*)
echo "Please use start or stop as first argument"
;;
esac

@ -0,0 +1,74 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
curPath=`pwd`
rootPath=$(dirname "$curPath")
rootPath=$(dirname "$rootPath")
serverPath=$(dirname "$rootPath")
sysName=`uname`
install_tmp=${rootPath}/tmp/bt_install.pl
#https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
Install_mac_ffmpeg()
{
if [ ! -f $serverPath/source/ffmpeg-20180702-3c4af57-macos64-static.zip ];then
wget -O $serverPath/source/ffmpeg-20180702-3c4af57-macos64-static.zip https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-20180702-3c4af57-macos64-static.zip
fi
if [ ! -d $serverPath/lib/ffmpeg ];then
cd $serverPath/source && tar -xvf $serverPath/source/ffmpeg-20180702-3c4af57-macos64-static.zip
mv ffmpeg-20180702-3c4af57-macos64-static $serverPath/lib/ffmpeg
fi
}
Install_linux_ffmpeg()
{
if [ ! -f $serverPath/source/ffmpeg-release-amd64-static.tar.xz ];then
wget -O $serverPath/source/ffmpeg-release-amd64-static.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
fi
if [ ! -d $serverPath/lib/ffmpeg ];then
cd $serverPath/source && tar -xvf $serverPath/source/ffmpeg-release-amd64-static.tar.xz
mv ffmpeg-4.1.2-amd64-static $serverPath/lib/ffmpeg
fi
}
Install_qbittorrent()
{
if [ $sysName == 'Darwin' ]; then
Install_mac_ffmpeg
else
yum -y install qbittorrent-nox
#qbittorrent-nox -d
Install_linux_ffmpeg
fi
pip install python-qbittorrent==0.2
echo '正在安装脚本文件...' > $install_tmp
mkdir -p $serverPath/qbittorrent
QB_DIR=${serverPath}/source/qbittorrent
mkdir -p $QB_DIR
echo '4.1.5' > $serverPath/qbittorrent/version.pl
echo '安装完成' > $install_tmp
}
Uninstall_qbittorrent()
{
rm -rf $serverPath/qbittorrent
which yum && yum -y remove qbittorrent-nox
echo "Uninstall_qbittorrent" > $install_tmp
}
action=$1
if [ "${1}" == 'install' ];then
Install_qbittorrent
else
Uninstall_qbittorrent
fi

@ -0,0 +1,186 @@
function qbPostMin(method, args, callback){
var req_data = {};
req_data['name'] = 'qbittorrent';
req_data['func'] = method;
if (typeof(args) != 'undefined' && args!=''){
req_data['args'] = JSON.stringify(args);
}
$.post('/plugins/run', req_data, function(data) {
if (!data.status){
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
return;
}
if(typeof(callback) == 'function'){
callback(data);
}
},'json');
}
function qbPost(method, args, callback){
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
qbPostMin(method,args,function(data){
layer.close(loadT);
if(typeof(callback) == 'function'){
callback(data);
}
});
}
function showHideHash(obj){
var a = "glyphicon-eye-open";
var b = "glyphicon-eye-close";
if($(obj).hasClass(a)){
$(obj).removeClass(a).addClass(b);
$(obj).prev().text($(obj).prev().attr('data-pw'))
} else{
$(obj).removeClass(b).addClass(a);
$(obj).prev().text($(obj).attr('data-pw'));
}
}
function copyText(password){
var clipboard = new ClipboardJS('#bt_copys');
clipboard.on('success', function (e) {
layer.msg('复制成功',{icon:1,time:2000});
});
clipboard.on('error', function (e) {
layer.msg('复制失败,浏览器不兼容!',{icon:2,time:2000});
});
$("#bt_copys").attr('data-clipboard-text',password);
$("#bt_copys").click();
}
function getLocalTime(nS) {
return new Date(parseInt(nS) * 1000).toLocaleString().replace(/:\d{1,2}$/,' ');
}
function qbAdd(){
var loadOpen = layer.open({
type: 1,
title: '添加资源',
area: '400px',
content:"<div class='bt-form pd20 pb70 c6'>\
<div class='version line'>\
<div><input class='bt-input-text mr5 outline_no' type='text' id='qb_hash' name='qb_hash' style='height: 28px; border-radius: 3px;width: 350px;' placeholder='hash'></div>\
</div>\
<div class='bt-form-submit-btn'>\
<button type='button' id='qb_close' class='btn btn-danger btn-sm btn-title'>关闭</button>\
<button type='button' id='qb_ok' class='btn btn-success btn-sm btn-title bi-btn'>确认</button>\
</div>\
</div>"
});
$('#qb_close').click(function(){
layer.close(loadOpen);
});
$('#qb_ok').click(function(){
var hash = $('#qb_hash').val();
qbPost('qb_add', {hash:hash}, function(data){
var rdata = $.parseJSON(data.data);
if (rdata['status']){
showMsg(rdata.msg, function(){
qbList();
},{icon:1,time:2000,shade: [0.3, '#000']});
layer.close(loadOpen);
} else {
layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']});
}
});
});
}
function qbDel(hash){
qbPost('qb_del', {hash:hash}, function(data){
var rdata = $.parseJSON(data.data);
if (rdata['status']){
layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']});
} else {
layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']});
}
});
}
function qbListFind(){
var qbs = $('#qb_selected').val();
if ( qbs == '0' ){
qbList();
} else {
qbList(qbs);
}
}
function openAdminUrl(){
qbPost('qb_url', '', function(data){
var rdata = $.parseJSON(data.data);
window.open(rdata.data);
});
}
function qbList(search){
var _data = {};
_data['test'] ='yes';
if(typeof(search) != 'undefined'){
_data['type'] = search;
}
qbPost('qb_list', _data, function(data){
var rdata = $.parseJSON(data.data);
if (!rdata['status']){
layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']});
return;
}
content = '<select id="qb_selected" class="bt-input-text mr20" style="width:30%;margin-bottom: 3px;">'+
'<option value="0">所有</option>' +
'<option value="completed">已下载</option>' +
'<option value="downloading">下载中</option>' +
'</select>';
content += '<button class="btn btn-success btn-sm" onclick="qbListFind();">查找</button></div>';
content += '<div class="divtable" style="margin-top:5px;"><table class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0">';
content += '<thead><tr>';
content += '<th>种子(hash)</th>';
content += '<th>添加时间</th>';
content += '<th>操作(<a class="btlink" onclick="qbAdd();">添加</a> | <a class="btlink" onclick="openAdminUrl();">管理</a>)</th>';
content += '</tr></thead>';
content += '<tbody>';
ulist = rdata.data.torrents;
for (i in ulist){
content += '<tr><td>'+
'<span class="password" data-pw="'+ulist[i]['hash']+'">'+ulist[i]['hash'].substr(0,3)+'</span>' +
'<span onclick="showHideHash(this)" data-pw="'+ulist[i]['hash'].substr(0,3)+'" class="glyphicon glyphicon-eye-open cursor pw-ico" style="margin-left:10px"></span>'+
'<span class="ico-copy cursor btcopy" style="margin-left:10px" title="复制种子hash" onclick="copyText(\''+ulist[i]['hash']+'\')"></span>'+
'</td>'+
'<td>'+getLocalTime(ulist[i]['added_on'])+'</td>'+
'<td><a class="btlink" onclick="qbDel(\''+ulist[i]['hash']+'\')">删除</a></td></tr>';
}
content += '</tbody>';
content += '</table></div>';
$(".soft-man-con").html(content);
var type = rdata.data.type;
if (type == ''){
$("#qb_selected option[value='0']").attr("selected", true);
} else {
$("#qb_selected option[value='"+type+"']").attr("selected", true);
}
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,719 @@
#!/usr/bin/env python
# encoding: utf-8
"""
下载检测
"""
'''
pl_hash_list 表字段 status说明
0 视频资源 - 正常
1 不含有视频资源
2 3小时下载未反应
4 3个月未下载完成
'''
import hashlib
import os
import time
import datetime
import traceback
import sys
import json
import socket
import threading
from hashlib import sha1
from random import randint
from struct import unpack
from socket import inet_ntoa
from threading import Timer, Thread
from time import sleep
reload(sys)
sys.setdefaultencoding("utf8")
sys.path.append('/usr/local/lib/python2.7/site-packages')
def formatTime():
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
def getRunDir():
return os.getcwd()
def getRootDir():
return os.path.dirname(os.path.dirname(getRunDir()))
def toSize(size):
# 字节单位转换
d = ('b', 'KB', 'MB', 'GB', 'TB')
s = d[0]
for b in d:
if size < 1024:
return str(round(size, 2)) + ' ' + b
size = float(size) / 1024.0
s = b
return str(round(size, 2)) + ' ' + b
# import pygeoip
import MySQLdb as mdb
from configparser import ConfigParser
cp = ConfigParser()
cp.read("../qb.conf")
section_db = cp.sections()[0]
DB_HOST = cp.get(section_db, "DB_HOST")
DB_USER = cp.get(section_db, "DB_USER")
DB_PORT = cp.getint(section_db, "DB_PORT")
DB_PASS = cp.get(section_db, "DB_PASS")
DB_NAME = cp.get(section_db, "DB_NAME")
section_qb = cp.sections()[1]
QB_HOST = cp.get(section_qb, "QB_HOST")
QB_PORT = cp.get(section_qb, "QB_PORT")
QB_USER = cp.get(section_qb, "QB_USER")
QB_PWD = cp.get(section_qb, "QB_PWD")
section_file = cp.sections()[2]
FILE_TO = cp.get(section_file, "FILE_TO")
FILE_TRANSFER_TO = cp.get(section_file, "FILE_TRANSFER_TO")
FILE_OWN = cp.get(section_file, "FILE_OWN")
FILE_GROUP = cp.get(section_file, "FILE_GROUP")
FILE_ENC_SWITCH = cp.get(section_file, "FILE_ENC_SWITCH")
FILE_API_URL = cp.get(section_file, "FILE_API_URL")
FILE_ASYNC_SWITCH = cp.get(section_file, "FILE_ASYNC_SWITCH")
section_task = cp.sections()[3]
TASK_SIZE_LIMIT = cp.get(section_task, "TASK_SIZE_LIMIT")
TASK_RATE = cp.getint(section_task, "TASK_RATE")
TASK_COMPLETED_RATE = cp.getint(section_task, "TASK_COMPLETED_RATE")
TASK_DEBUG = cp.getint(section_task, "TASK_DEBUG")
section_setting = cp.sections()[4]
QUEUE_SWITCH = cp.get(section_setting, "QUEUE_SWITCH")
MAX_ACTIVE_UPLOADS = cp.getint(section_setting, "MAX_ACTIVE_UPLOADS")
MAX_ACTIVE_TORRENTS = cp.getint(section_setting, "MAX_ACTIVE_TORRENTS")
MAX_ACTIVE_DOWNLOADS = cp.getint(section_setting, "MAX_ACTIVE_DOWNLOADS")
rooDir = getRootDir()
ffmpeg_cmd = rooDir + "/lib/ffmpeg/ffmpeg"
if not os.path.exists(ffmpeg_cmd):
ffmpeg_cmd = rooDir + "/lib/ffmpeg/bin/ffmpeg"
class downloadBT(Thread):
__db_err = None
def __init__(self):
Thread.__init__(self)
self.setDaemon(True)
self.qb = self.qb()
self.qb.set_preferences(max_active_uploads=MAX_ACTIVE_UPLOADS)
self.qb.set_preferences(max_active_torrents=MAX_ACTIVE_TORRENTS)
self.qb.set_preferences(max_active_downloads=MAX_ACTIVE_DOWNLOADS)
_has_suffix = ['mp4', 'rmvb', 'flv', 'avi',
'mpg', 'mkv', 'wmv', 'avi', 'rm']
has_suffix = []
for x in range(len(_has_suffix)):
has_suffix.append('.' + _has_suffix[x])
has_suffix.append('.' + _has_suffix[x].upper())
self.has_suffix = has_suffix
def __conn(self):
try:
self.dbconn = mdb.connect(
DB_HOST, DB_USER, DB_PASS, DB_NAME, port=DB_PORT, charset='utf8')
self.dbconn.autocommit(False)
self.dbcurr = self.dbconn.cursor()
self.dbcurr.execute('SET NAMES utf8')
return True
except Exception as e:
self.__db_err = e
return False
def __close(self):
self.dbcurr.close()
self.dbconn.close()
def query(self, sql):
# 执行SQL语句返回数据集
if not self.__conn():
return self.__db_err
try:
self.dbcurr.execute(sql)
result = self.dbcurr.fetchall()
# print result
# 将元组转换成列表
data = map(list, result)
self.__close()
return data
except Exception, ex:
return ex
def execute(self, sql):
if not self.__conn():
return self.__db_err
try:
result = self.dbcurr.execute(sql)
self.dbconn.commit()
self.__close()
return result
except Exception, ex:
return ex
def qb(self):
from qbittorrent import Client
url = 'http://' + QB_HOST + ':' + QB_PORT + '/'
qb = Client(url)
qb.login(QB_USER, QB_PWD)
return qb
def execShell(self, cmdstring, cwd=None, timeout=None, shell=True):
import subprocess
if shell:
cmdstring_list = cmdstring
else:
cmdstring_list = shlex.split(cmdstring)
if timeout:
end_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
sub = subprocess.Popen(cmdstring_list, cwd=cwd, stdin=subprocess.PIPE,
shell=shell, bufsize=4096, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while sub.poll() is None:
time.sleep(0.1)
if timeout:
if end_time <= datetime.datetime.now():
raise Exception("Timeout:%s" % cmdstring)
return sub.communicate()
def md5(self, str):
# 生成MD5
try:
m = hashlib.md5()
m.update(str)
return m.hexdigest()
except:
return False
def readFile(self, filename):
# 读文件内容
try:
fp = open(filename, 'r')
fBody = fp.read()
fp.close()
return fBody
except:
return False
def get_transfer_ts_file(self, to):
return FILE_TRANSFER_TO + '/' + to + '.ts'
def get_transfer_mp4_file(self, to):
return FILE_TRANSFER_TO + '/' + to + '.mp4'
def get_transfer_m3u5_dir(self, dirname, fname):
return FILE_TO + '/m3u8/' + dirname + '/' + fname
def get_transfer_hash_dir(self, dirname):
return FILE_TO + '/m3u8/' + dirname
def fg_transfer_mp4_cmd(self, sfile, dfile):
cmd = ffmpeg_cmd + ' -y -i "' + sfile + \
'" -threads 1 -preset veryslow -crf 28 -c:v libx264 -strict -2 ' + dfile
return cmd
def fg_transfer_ts_cmd(self, file, to_file):
cmd = ffmpeg_cmd + ' -y -i ' + file + \
' -s 480x360 -vcodec copy -acodec copy -vbsf h264_mp4toannexb ' + to_file
return cmd
def fg_m3u8_cmd(self, ts_file, m3u8_file, to_file):
cmd = ffmpeg_cmd + ' -y -i ' + ts_file + ' -c copy -map 0 -f segment -segment_list ' + \
m3u8_file + ' -segment_time 3 ' + to_file
return cmd
def fg_m3u8enc_cmd(self, ts_file, m3u8_file, to_file, enc_dir):
cmd = ffmpeg_cmd + ' -y -i ' + ts_file + ' -threads 1 -strict -2 -hls_time 3 -hls_key_info_file ' + \
enc_dir + '/enc.keyinfo.txt -hls_playlist_type vod -hls_segment_filename ' + \
to_file + ' ' + m3u8_file
return cmd
def debug(self, msg):
return formatTime() + ":" + msg
def get_lock_file(self, to):
return '/tmp/mdw_qb_' + to + '.lock'
def lock(self, sign):
l = self.get_lock_file(sign)
self.execShell('touch ' + l)
def unlock(self, sign):
l = self.get_lock_file(sign)
self.execShell('rm -rf ' + l)
def islock(self, sign):
l = self.get_lock_file(sign)
if os.path.exists(l):
return True
return False
def ffmpeg_file_sync(self):
if FILE_ASYNC_SWITCH == '1':
runDir = getRunDir()
sign = 'sync'
print 'file_sync... start'
if self.islock(sign):
print self.debug('sync doing,already lock it!!!')
else:
self.lock(sign)
r = self.execShell('sh -x ' + runDir + '/rsync.sh')
print self.debug('file_sync:' + r[0])
print self.debug('file_sync_error:' + r[1])
self.unlock(sign)
print 'file_sync... end'
def ffmpeg_del_file(self, mp4, ts, m3u8_dir):
print self.debug('delete middle file ... start' + mp4)
self.execShell('rm -rf ' + mp4)
self.execShell('rm -rf ' + ts)
if os.path.exists(m3u8_dir):
self.execShell('rm -rf ' + m3u8_dir)
print self.debug('delete middle file ... end' + ts)
def ffmpeg_del_hfile(self, shash_dir):
print self.debug('delete middle hash dir ... start' + shash_dir)
if os.path.exists(shash_dir):
self.execShell('rm -rf ' + shash_dir)
print self.debug('delete middle hash dir ... end' + shash_dir)
def ffmpeg(self, file=''):
if not os.path.exists(FILE_TRANSFER_TO):
self.execShell('mkdir -p ' + FILE_TRANSFER_TO)
fname = os.path.basename(file)
shash = self.sign_torrent['hash']
md5file = self.md5(file)
if not os.path.exists(file):
print formatTime(), 'file not exists:', file
return
print self.debug('source file ' + file)
mp4file = self.get_transfer_mp4_file(md5file)
cmd_mp4 = self.fg_transfer_mp4_cmd(file, mp4file)
if not os.path.exists(mp4file):
print self.debug('cmd_mp4:' + cmd_mp4)
os.system(cmd_mp4)
else:
print self.debug('mp4 exists:' + mp4file)
if not os.path.exists(mp4file):
print self.debug('mp4 not exists')
return
tsfile = self.get_transfer_ts_file(md5file)
cmd_ts = self.fg_transfer_ts_cmd(mp4file, tsfile)
if not os.path.exists(tsfile):
print self.debug('cmd_ts:' + cmd_ts)
os.system(cmd_ts)
else:
print self.debug('data_ts exists:' + mp4file)
if not os.path.exists(tsfile):
print self.debug('ts not exists')
return
md5Fname = self.md5(fname)
m3u8_dir = self.get_transfer_m3u5_dir(shash, md5Fname)
if not os.path.exists(m3u8_dir):
self.execShell('mkdir -p ' + m3u8_dir)
m3u8_file = m3u8_dir + '/index.m3u8'
tofile = m3u8_dir + '/%010d.ts'
print self.debug('tofile:' + tofile)
# 加密m3u8
if FILE_ENC_SWITCH != '0':
enc_dir = '/tmp/qb_m3u8'
cmd = self.fg_m3u8enc_cmd(tsfile, m3u8_file, tofile, enc_dir)
if os.path.exists(m3u8_file):
print self.debug('cmd_m3u8_enc exists:' + m3u8_file)
print self.debug('cmd_m3u8_enc:' + cmd)
self.ffmpeg_file_sync()
self.ffmpeg_del_file(mp4file, tsfile, m3u8_dir)
return
self.execShell('mkdir -p ' + enc_dir)
self.execShell('openssl rand -base64 16 > ' +
enc_dir + '/enc.key')
self.execShell('rm -rf ' + enc_dir + '/enc.keyinfo.txt')
try:
fid = self.add_hash(fname, md5Fname)
except Exception as e:
print 'add_hash_enc:' + str(e)
return
fid = self.add_hash(fname, md5Fname)
key = self.readFile(enc_dir + '/enc.key').strip()
self.set_hashfile_key(fid, key)
# FILE_API_URL
url = FILE_API_URL.replace('{$KEY}', fid)
enc_url = 'echo ' + url + ' >> ' + enc_dir + '/enc.keyinfo.txt'
self.execShell(enc_url)
enc_path = 'echo ' + enc_dir + '/enc.key >> ' + enc_dir + '/enc.keyinfo.txt'
self.execShell(enc_path)
enc_iv = 'openssl rand -hex 16 >> ' + enc_dir + '/enc.keyinfo.txt'
self.execShell(enc_iv)
os.system(cmd)
else:
if os.path.exists(m3u8_file):
print self.debug('m3u8 exists:' + tofile)
self.ffmpeg_file_sync()
self.ffmpeg_del_file(mp4file, tsfile, m3u8_dir)
return
cmd_m3u8 = self.fg_m3u8_cmd(tsfile, m3u8_file, tofile)
print self.debug('cmd_m3u8:' + cmd_m3u8)
os.system(cmd_m3u8)
try:
self.add_hash(fname, md5Fname)
except Exception as e:
print 'add_hash', str(e)
self.execShell('chown -R ' + FILE_OWN + ':' +
FILE_GROUP + ' ' + m3u8_dir)
self.execShell('chmod -R 755 ' + m3u8_dir)
self.ffmpeg_file_sync()
self.ffmpeg_del_file(mp4file, tsfile, m3u8_dir)
def get_bt_size(self, torrent):
total_size = '0'
if 'size' in torrent:
total_size = str(torrent['size'])
if 'total_size' in torrent:
total_size = str(torrent['total_size'])
return total_size
def get_hashlist_id(self):
ct = formatTime()
total_size = self.get_bt_size(self.sign_torrent)
shash = self.sign_torrent['hash']
sname = self.sign_torrent['name']
sname = mdb.escape_string(sname)
info = self.query(
"select id from pl_hash_list where info_hash='" + shash + "'")
if len(info) > 0:
pid = str(info[0][0])
else:
print 'insert into pl_hash_list data'
pid = self.execute("insert into pl_hash_list (`name`,`info_hash`,`length`,`create_time`) values('" +
sname + "','" + shash + "','" + total_size + "','" + ct + "')")
return pid
def set_hashlist_status(self, torrent, status):
ct = formatTime()
shash = torrent['hash']
info = self.query(
"select id from pl_hash_list where info_hash='" + shash + "'")
if len(info) > 0:
print 'set_hashlist_status update'
usql = "update pl_hash_list set `status`='" + \
str(status) + "' where info_hash='" + shash + "'"
self.execute(usql)
else:
print 'set_hashlist_status insert'
total_size = self.get_bt_size(torrent)
sname = torrent['name']
sname = mdb.escape_string(sname)
return self.execute("insert into pl_hash_list (`name`,`info_hash`,`length`,`status`,`create_time`) values('" +
sname + "','" + shash + "','" + total_size + "','" + str(status) + "','" + ct + "')")
def get_hashfile_id(self, fname, m3u8_name, pid):
ct = formatTime()
info = self.query(
"select id from pl_hash_file where name='" + fname + "' and pid='" + pid + "'")
if len(info) == 0:
print 'insert into pl_hash_file data !'
fid = self.execute("insert into pl_hash_file (`pid`,`name`,`m3u8`,`create_time`) values('" +
pid + "','" + fname + "','" + m3u8_name + "','" + ct + "')")
else:
print fname, ':', m3u8_name, 'already is exists!'
fid = str(info[0][0])
return fid
def set_hashfile_key(self, fid, key):
self.execute("update pl_hash_file set `key`='" +
mdb.escape_string(key) + "' where id=" + fid)
def add_queue(self, shash, size):
ct = formatTime()
info = self.query(
"select id from pl_hash_queue where info_hash='" + shash + "'")
if len(info) == 0:
sql = "insert into pl_hash_queue (`info_hash`,`length`,`created_at`,`updated_at`) values('" + \
shash + "','" + str(size) + "','" + ct + "','" + ct + "')"
return self.execute(sql)
else:
print 'queue:', shash, 'already is exists!'
def add_hash(self, fname, m3u8_name):
print '-------------------------add_hash---start-----------------------'
pid = self.get_hashlist_id()
fid = self.get_hashfile_id(fname, m3u8_name, pid)
print '-------------------------add_hash---end--------------------------'
return fid
def file_arr(self, path, filters=['.DS_Store']):
file_list = []
flist = os.listdir(path)
for i in range(len(flist)):
# 下载缓存文件过滤
if flist[i] == '.unwanted':
continue
file_path = os.path.join(path, flist[i])
if flist[i] in filters:
continue
if os.path.isdir(file_path):
tmp = self.file_arr(file_path, filters)
file_list.extend(tmp)
else:
file_list.append(file_path)
return file_list
def find_dir_video(self, path):
flist = self.file_arr(path)
video = []
for i in range(len(flist)):
if self.is_video(flist[i]):
video.append(flist[i])
return video
def video_do(self, path):
if os.path.isfile(path):
if self.is_video(path):
self.ffmpeg(path)
else:
vlist = self.find_dir_video(path)
for v in vlist:
self.ffmpeg(v)
return ''
def is_video(self, path):
t = os.path.splitext(path)
if t[1] in self.has_suffix:
return True
return False
def non_download(self, torrent):
flist = self.qb.get_torrent_files(torrent['hash'])
is_video = False
for pos in range(len(flist)):
file = torrent['save_path'] + flist[pos]['name']
if not self.is_video(file):
self.qb.set_file_priority(torrent['hash'], pos, 0)
else:
is_video = True
# is video
if not is_video:
self.set_status(torrent, 1)
def set_status(self, torrent, status):
self.set_hashlist_status(torrent, status)
if TASK_DEBUG == 0 and status != 0:
self.qb.delete_permanently(torrent['hash'])
def is_downloading(self, torrent):
if torrent['name'] == torrent['hash']:
return True
else:
return False
def is_nondownload_overtime(self, torrent, sec):
ct = time.time()
use_time = int(ct) - int(torrent['added_on'])
flist = self.qb.get_torrent_files(torrent['hash'])
# print flist
flist_len = len(flist)
# 没有获取种子信息
# print 'ddd:',flist_len,use_time,sec
if flist_len == 0 and use_time > sec:
self.set_status(torrent, 2)
return True
is_video_download = False
# 获取了种子信息,但是没有下载
for pos in range(len(flist)):
file = torrent['save_path'] + flist[pos]['name']
if self.is_video(file):
if flist[pos]['progress'] != '0':
is_video_download = True
if not is_video_download and use_time > sec:
self.set_status(torrent, 3)
return True
return False
def is_downloading_overtime(self, torrent, sec):
ct = time.time()
use_time = int(ct) - int(torrent['added_on'])
if use_time > sec:
self.set_status(torrent, 4)
return True
return False
def is_downloading_overlimit(self, torrent):
sz = self.get_bt_size(torrent)
ct = formatTime()
size_limit = float(TASK_SIZE_LIMIT) * 1024 * 1024 * 1024
size_limit = int(size_limit)
print 'is_downloading_overlimit:', toSize(sz), toSize(size_limit)
if int(sz) > int(size_limit):
print 'overlimit sz:' + sz
self.add_queue(torrent['hash'], str(sz))
self.qb.delete_permanently(torrent['hash'])
def check_task(self):
while True:
torrents = self.qb.torrents(filter='downloading')
tlen = len(torrents)
if tlen > 0:
print "downloading torrents count:", tlen
for torrent in torrents:
if self.is_nondownload_overtime(torrent, 5 * 60):
pass
elif self.is_downloading_overtime(torrent, 7 * 24 * 60 * 60):
pass
elif self.is_downloading(torrent):
pass
elif self.is_downloading_overlimit(torrent):
pass
else:
self.non_download(torrent)
print torrent['name'], ' task downloading!'
else:
print self.debug("no downloading task!")
time.sleep(TASK_RATE)
def completed(self):
while True:
torrents = self.qb.torrents(filter='completed')
tlen = len(torrents)
print "completed torrents count:", tlen
if tlen > 0:
for torrent in torrents:
self.sign_torrent = torrent
path = torrent['save_path'] + torrent['name']
path = path.encode()
try:
self.video_do(path)
hash_dir = self.get_transfer_hash_dir(torrent['hash'])
self.ffmpeg_del_hfile(hash_dir)
if TASK_DEBUG == 0:
self.qb.delete_permanently(torrent['hash'])
except Exception as e:
print formatTime(), str(e)
print self.debug("done task!")
else:
print self.debug("no completed task!")
time.sleep(TASK_COMPLETED_RATE)
def add_hash_task(self, shash):
url = 'magnet:?xt=urn:btih:' + shash
self.qb.download_from_link(url)
print self.debug('queue add_hash_task is ok ... ')
def queue(self):
while True:
if QUEUE_SWITCH == '1':
print self.debug("------------ do queue task start! ---------------")
setting = self.qb.preferences()
torrents = self.qb.torrents()
tlen = len(torrents)
# print tlen, setting['max_active_torrents']
add = int(setting['max_active_torrents']) - tlen
if add == 0:
print self.debug('the download queue is full ... ')
else:
size_limit = float(TASK_SIZE_LIMIT) * 1024 * 1024 * 1024
size_limit = int(size_limit)
size_sql_where = ''
size_sql = ''
if size_limit != 0:
size_sql = ',`length` desc '
size_sql_where = 'where `length`<=' + str(size_limit)
sql = "select * from pl_hash_queue " + size_sql_where + \
" order by created_at desc " + \
size_sql + " limit " + str(add)
info = self.query(sql)
info_len = len(info)
if info_len == 0:
print self.debug('queue data is empty ... ')
else:
for x in range(info_len):
self.add_hash_task(info[x][1])
self.execute(
'delete from pl_hash_queue where id=' + str(info[x][0]))
print self.debug("------------ do queue task end ! ---------------")
time.sleep(TASK_RATE)
def test():
while True:
print self.debug("no download task!")
time.sleep(1)
test()
if __name__ == "__main__":
dl = downloadBT()
import threading
check_task = threading.Thread(target=dl.check_task)
check_task.start()
completed = threading.Thread(target=dl.completed)
completed.start()
queue = threading.Thread(target=dl.queue)
queue.start()

@ -0,0 +1,5 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo 'hello world'
Loading…
Cancel
Save