#!/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 . @author: RaNaN """ from copy import deepcopy from datetime import datetime from itertools import chain from operator import itemgetter import os import sqlite3 import time from os import listdir 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, 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.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(time.time() + 60 * 60 * 24 * 7)) response.headers['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() new_time = request.POST.get("Accounts|%s|time;%s" % (pluginname, data["login"]), "").strip() if newpw or (new_time and (not data["options"].has_key("time") or [new_time] != data["options"]["time"])): PYLOAD.update_account(pluginname, data["login"], newpw, {"time": [new_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])