summaryrefslogtreecommitdiffstats
path: root/module/web/pyload_app.py
diff options
context:
space:
mode:
Diffstat (limited to 'module/web/pyload_app.py')
-rw-r--r--module/web/pyload_app.py483
1 files changed, 483 insertions, 0 deletions
diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py
new file mode 100644
index 000000000..ab0cbfb00
--- /dev/null
+++ b/module/web/pyload_app.py
@@ -0,0 +1,483 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ 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/>.
+
+ @author: RaNaN
+"""
+from copy import deepcopy
+import datetime
+from datetime import datetime
+from itertools import chain
+from operator import itemgetter
+import os
+
+import sqlite3
+import time
+from os import listdir
+from os import stat
+from os.path import isdir
+from os.path import isfile
+from os.path import join
+from sys import getfilesystemencoding
+from hashlib import sha1
+from urllib import unquote
+
+from bottle import route, static_file, request, response, redirect, HTTPError
+
+from webinterface import PYLOAD, PROJECT_DIR
+
+from utils import render_to_response, parse_permissions, parse_userdata, formatSize, login_required
+from filters import relpath, quotepath, unquotepath
+
+# Helper
+
+def pre_processor():
+ s = request.environ.get('beaker.session')
+ user = parse_userdata(s)
+ perms = parse_permissions(s)
+ return {"user": user,
+ 'status': PYLOAD.status_server(),
+ 'captcha': PYLOAD.is_captcha_waiting(),
+ 'perms': perms}
+
+
+def get_sort_key(item):
+ return item[1]["order"]
+
+
+def base(messages):
+ return render_to_response('base.html', {'messages': messages}, [pre_processor])
+
+
+## Views
+
+@route('/media/:path#.+#')
+def server_static(path):
+ response.header['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(time.time() + 60 * 60 * 24 * 7))
+ response.header['Cache-control'] = "public"
+ return static_file(path, root=join(PROJECT_DIR, "media"))
+
+@route('/favicon.ico')
+def favicon():
+ return static_file("favicon.ico", root=join(PROJECT_DIR, "media", "img"))
+
+@route('/login', method="GET")
+def login():
+ return render_to_response("login.html", proc=[pre_processor])
+
+@route("/login", method="POST")
+def login_post():
+ user = request.forms.get("username")
+ password = request.forms.get("password")
+
+ conn = sqlite3.connect('web.db')
+ c = conn.cursor()
+ c.execute('SELECT name, password, role, permission,template FROM "users" WHERE name=?', (user,))
+ r = c.fetchone()
+ c.close()
+ conn.commit()
+ conn.close()
+
+ if not r:
+ return render_to_response("login.html", {"errors": True}, [pre_processor])
+
+ salt = r[1][:5]
+ pw = r[1][5:]
+
+ hash = sha1(salt + password)
+ if hash.hexdigest() == pw:
+ s = request.environ.get('beaker.session')
+ s["authenticated"] = True
+ s["name"] = r[0]
+ s["role"] = r[2]
+ s["perms"] = r[3]
+ s["template"] = r[4]
+ s.save()
+
+ return redirect("/")
+
+
+ else:
+ return render_to_response("login.html", {"errors": True}, [pre_processor])
+
+@route("/logout")
+def logout():
+ s = request.environ.get('beaker.session')
+ s.delete()
+ return render_to_response("logout.html", proc=[pre_processor])
+
+
+@route("/")
+@route("/home")
+@login_required("can_see_dl")
+def home():
+ res = PYLOAD.status_downloads()
+
+ for link in res:
+ if link["status"] == 12:
+ link["information"] = "%s kB @ %s kB/s" % (link["size"] - link["kbleft"], link["speed"])
+
+ return render_to_response("home.html", {"res": res}, [pre_processor])
+
+
+@route("/queue")
+@login_required("can_see_dl")
+def queue():
+ queue = PYLOAD.get_queue_info()
+
+ data = zip(queue.keys(), queue.values())
+ data.sort(key=get_sort_key)
+
+ return render_to_response('queue.html', {'content': data}, [pre_processor])
+
+@route("/collector")
+@login_required('can_see_dl')
+def collector():
+ queue = PYLOAD.get_collector_info()
+
+ data = zip(queue.keys(), queue.values())
+ data.sort(key=get_sort_key)
+
+ return render_to_response('collector.html', {'content': data}, [pre_processor])
+
+@route("/downloads")
+@login_required('can_download')
+def downloads():
+ root = PYLOAD.get_conf_val("general", "download_folder")
+
+ if not isdir(root):
+ return base([_('Download directory not found.')])
+ data = {
+ 'folder': [],
+ 'files': []
+ }
+
+ for item in sorted(listdir(root)):
+ if isdir(join(root, item)):
+ folder = {
+ 'name': item,
+ 'path': item,
+ 'files': []
+ }
+ for file in sorted(listdir(join(root, item))):
+ try:
+ if isfile(join(root, item, file)):
+ folder['files'].append(file)
+ except:
+ pass
+
+ data['folder'].append(folder)
+ elif isfile(join(root, item)):
+ data['files'].append(item)
+
+ return render_to_response('downloads.html', {'files': data}, [pre_processor])
+
+@route("/downloads/get/:path#.+#")
+@login_required("can_download")
+def get_download(path):
+ path = unquote(path)
+ #@TODO some files can not be downloaded
+
+ root = PYLOAD.get_conf_val("general", "download_folder")
+
+ path = path.replace("..", "")
+ try:
+ return static_file(path, root)
+
+ except Exception, e:
+ print e
+ return HTTPError(404, "File not Found.")
+
+@route("/settings")
+@route("/settings", method="POST")
+@login_required('can_change_status')
+def config():
+ conf = PYLOAD.get_config()
+ plugin = PYLOAD.get_plugin_config()
+ accs = PYLOAD.get_accounts()
+ messages = []
+
+ for section in chain(conf.itervalues(), plugin.itervalues()):
+ for key, option in section.iteritems():
+ if key == "desc": continue
+
+ if ";" in option["type"]:
+ option["list"] = option["type"].split(";")
+
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ errors = []
+
+ for key, value in request.POST.iteritems():
+ if not "|" in key: continue
+ sec, skey, okey = key.split("|")[:]
+
+ if sec == "General":
+ if conf.has_key(skey):
+ if conf[skey].has_key(okey):
+ try:
+ if str(conf[skey][okey]['value']) != value:
+ PYLOAD.set_conf_val(skey, okey, value)
+ except Exception, e:
+ errors.append("%s | %s : %s" % (skey, okey, e))
+ else:
+ continue
+ else:
+ continue
+
+ elif sec == "Plugin":
+ if plugin.has_key(skey):
+ if plugin[skey].has_key(okey):
+ try:
+ if str(plugin[skey][okey]['value']) != value:
+ PYLOAD.set_conf_val(skey, okey, value, "plugin")
+ except Exception, e:
+ errors.append("%s | %s : %s" % (skey, okey, e))
+ else:
+ continue
+ else:
+ continue
+ elif sec == "Accounts":
+ if ";" in okey:
+ action, name = okey.split(";")
+ if action == "delete":
+ PYLOAD.remove_account(skey, name)
+
+ if okey == "newacc" and value:
+ # add account
+
+ pw = request.POST.get("Accounts|%s|newpw" % skey)
+ PYLOAD.update_account(skey, value, pw)
+
+ for pluginname, accdata in accs.iteritems():
+ for data in accdata:
+ newpw = request.POST.get("Accounts|%s|password;%s" % (pluginname, data["login"]), "").strip()
+ time = request.POST.get("Accounts|%s|time;%s" % (pluginname, data["login"]), "").strip()
+
+ if newpw or (time and (not data["options"].has_key("time") or [time] != data["options"]["time"])):
+ PYLOAD.update_account(pluginname, data["login"], newpw, {"time": [time]})
+
+ if errors:
+ messages.append(_("Error occured when setting the following options:"))
+ messages.append("")
+ messages += errors
+ else:
+ messages.append(_("All options were set correctly."))
+
+ accs = deepcopy(PYLOAD.get_accounts(False, False))
+ for accounts in accs.itervalues():
+ for data in accounts:
+ if data["trafficleft"] == -1:
+ data["trafficleft"] = _("unlimited")
+ elif not data["trafficleft"]:
+ data["trafficleft"] = _("not available")
+ else:
+ data["trafficleft"] = formatSize(data["trafficleft"])
+
+ if data["validuntil"] == -1:
+ data["validuntil"] = _("unlimited")
+ elif not data["validuntil"]:
+ data["validuntil"] = _("not available")
+ else:
+ t = time.localtime(data["validuntil"])
+ data["validuntil"] = time.strftime("%d.%m.%Y", t)
+
+ if data["options"].has_key("time"):
+ try:
+ data["time"] = data["options"]["time"][0]
+ except:
+ data["time"] = "invalid"
+
+
+ return render_to_response('settings.html',
+ {'conf': {'Plugin': plugin, 'General': conf, 'Accounts': accs}, 'errors': messages},
+ [pre_processor])
+
+@route("/package_ui.js")
+@login_required('can_see_dl')
+def package_ui():
+ response.header['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(time.time() + 60 * 60 * 24 * 7))
+ response.header['Cache-control'] = "public"
+ return render_to_response('package_ui.js')
+
+
+@route("/filechooser")
+@route("/pathchooser")
+@route("/filechooser/:file#.+#")
+@route("/pathchooser/:path#.+#")
+@login_required('can_change_status')
+def path(file="", path=""):
+ if file:
+ type = "file"
+ else:
+ type = "folder"
+
+ path = os.path.normpath(unquotepath(path))
+
+ if os.path.isfile(path):
+ oldfile = path
+ path = os.path.dirname(path)
+ else:
+ oldfile = ''
+
+ abs = False
+
+ if os.path.isdir(path):
+ if os.path.isabs(path):
+ cwd = os.path.abspath(path)
+ abs = True
+ else:
+ cwd = relpath(path)
+ else:
+ cwd = os.getcwd()
+
+ try:
+ cwd = cwd.encode("utf8")
+ except:
+ pass
+
+ cwd = os.path.normpath(os.path.abspath(cwd))
+ parentdir = os.path.dirname(cwd)
+ if not abs:
+ if os.path.abspath(cwd) == "/":
+ cwd = relpath(cwd)
+ else:
+ cwd = relpath(cwd) + os.path.sep
+ parentdir = relpath(parentdir) + os.path.sep
+
+ if os.path.abspath(cwd) == "/":
+ parentdir = ""
+
+ try:
+ folders = os.listdir(cwd)
+ except:
+ folders = []
+
+ files = []
+
+ for f in folders:
+ try:
+ f = f.decode(getfilesystemencoding())
+ data = {}
+ data['name'] = f
+ data['fullpath'] = join(cwd, f)
+ data['sort'] = data['fullpath'].lower()
+ data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f))))
+ data['ext'] = os.path.splitext(f)[1]
+ except:
+ continue
+
+ if os.path.isdir(join(cwd, f)):
+ data['type'] = 'dir'
+ else:
+ data['type'] = 'file'
+
+ if os.path.isfile(join(cwd, f)):
+ data['size'] = os.path.getsize(join(cwd, f))
+
+ power = 0
+ while (data['size']/1024) > 0.3:
+ power += 1
+ data['size'] /= 1024.
+ units = ('', 'K','M','G','T')
+ data['unit'] = units[power]+'Byte'
+ else:
+ data['size'] = ''
+
+ files.append(data)
+
+ files = sorted(files, key=itemgetter('type', 'sort'))
+
+ return render_to_response('pathchooser.html', {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile, 'absolute': abs}, [])
+
+@route("/logs")
+@route("/logs/:item")
+@route("/logs/:item", method="POST")
+@login_required('can_see_logs')
+def logs(item=-1):
+ s = request.environ.get('beaker.session')
+
+ perpage = s.get('perpage', 34)
+ reversed = s.get('reversed', False)
+
+ warning = ""
+ conf = PYLOAD.get_config()
+ if not conf['log']['file_log']['value']:
+ warning = "Warning: File log is disabled, see settings page."
+
+ perpage_p = ((20,20), (34, 34), (40, 40), (100, 100), (0,'all'))
+ fro = None
+
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ try:
+ fro = datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S')
+ except:
+ pass
+ try:
+ perpage = int(request.forms['perpage'])
+ s['perpage'] = perpage
+
+ reversed = bool(request.forms.get('reversed', False))
+ s['reversed'] = reversed
+ except:
+ pass
+
+ s.save()
+
+ try:
+ item = int(item)
+ except:
+ pass
+
+ log = PYLOAD.get_log()
+ if not perpage:
+ item = 0
+
+ if item < 1 or type(item) is not int:
+ item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1
+
+ if type(fro) is datetime: # we will search for datetime
+ item = -1
+
+ data = []
+ counter = 0
+ perpagecheck = 0
+ for l in log:
+ counter += 1
+
+ if counter >= item:
+ try:
+ date,time,level,message = l.split(" ", 3)
+ dtime = datetime.strptime(date+' '+time, '%d.%m.%Y %H:%M:%S')
+ except:
+ dtime = None
+ date = '?'
+ time = ' '
+ level = '?'
+ message = l
+ if item == -1 and dtime is not None and fro <= dtime:
+ item = counter #found our datetime
+ if item >= 0:
+ data.append({'line': counter, 'date': date+" "+time, 'level':level, 'message': message})
+ perpagecheck += 1
+ if fro is None and dtime is not None: #if fro not set set it to first showed line
+ fro = dtime
+ if perpagecheck >= perpage > 0:
+ break
+
+ if fro is None: #still not set, empty log?
+ fro = datetime.now()
+ if reversed:
+ data.reverse()
+ return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'), 'reversed': reversed, 'perpage':perpage, 'perpage_p':sorted(perpage_p), 'iprev': 1 if item - perpage < 1 else item - perpage, 'inext': (item + perpage) if item+perpage < len(log) else item}, [pre_processor]) \ No newline at end of file