From f3e18e3fef2b0a6d7db26ddc7c9f025cdae7ef23 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Wed, 2 Jan 2013 21:15:57 +0100 Subject: New naming: pyLoadCore -> pyload pyLoadCli -> pyload-cli --- docs/conf.py | 2 +- module/AddonManager.py | 4 +- module/config/ConfigManager.py | 2 +- pavement.py | 4 +- pyLoadCli.py | 590 ------------------------------------ pyLoadCore.py | 673 ----------------------------------------- pyload-cli.py | 590 ++++++++++++++++++++++++++++++++++++ pyload.py | 673 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1268 insertions(+), 1270 deletions(-) delete mode 100755 pyLoadCli.py delete mode 100755 pyLoadCore.py create mode 100755 pyload-cli.py create mode 100755 pyload.py diff --git a/docs/conf.py b/docs/conf.py index fc1876368..258dab745 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ master_doc = 'index' # General information about the project. project = u'pyLoad' -copyright = u'2012, pyLoad Team' +copyright = u'2013, pyLoad Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/module/AddonManager.py b/module/AddonManager.py index ccb617dc6..7d3852274 100644 --- a/module/AddonManager.py +++ b/module/AddonManager.py @@ -43,7 +43,7 @@ class AddonManager: self.lock = RLock() self.createIndex() - # manage addons an config change + # manage addons on config change self.addEvent("configChanged", self.manageAddons) @lock @@ -224,8 +224,6 @@ class AddonManager: def addEventListener(self, plugin, func, event): """ add the event to the list """ - - if plugin not in self.events: self.events[plugin] = [] self.events[plugin].append((func, event)) diff --git a/module/config/ConfigManager.py b/module/config/ConfigManager.py index 3ee66bc85..872ce2e00 100644 --- a/module/config/ConfigManager.py +++ b/module/config/ConfigManager.py @@ -98,7 +98,7 @@ class ConfigManager(ConfigParser): self.values[user, section][option] = value self.saveValues(user, section) - if changed: self.core.evm.dispatchEvent("configChanged", value) + if changed: self.core.evm.dispatchEvent("configChanged", section, option, value) return changed def saveValues(self, user, section): diff --git a/pavement.py b/pavement.py index e6e789882..7919cf52a 100644 --- a/pavement.py +++ b/pavement.py @@ -70,8 +70,8 @@ setup( #setup_requires=["setuptools_hg"], entry_points={ 'console_scripts': [ - 'pyLoadCore = pyLoadCore:main', - 'pyLoadCli = pyLoadCli:main' + 'pyload = pyload:main', + 'pyload-cli = pyload-cli:main' ]}, zip_safe=False, classifiers=[ diff --git a/pyLoadCli.py b/pyLoadCli.py deleted file mode 100755 index cf8fabd1a..000000000 --- a/pyLoadCli.py +++ /dev/null @@ -1,590 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -#Copyright (C) 2012 RaNaN -# -#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 . -# -### -from __future__ import with_statement -from getopt import GetoptError, getopt - -import module.common.pylgettext as gettext -import os -from os import _exit -from os.path import join, exists, abspath, basename -import sys -from sys import exit -from threading import Thread, Lock -from time import sleep -from traceback import print_exc - -import ConfigParser - -from codecs import getwriter - -if os.name == "nt": - enc = "cp850" -else: - enc = "utf8" - -sys.stdout = getwriter(enc)(sys.stdout, errors="replace") - -from module import InitHomeDir -from module.cli.printer import * -from module.cli import AddPackage, ManageFiles - -from module.Api import Destination -from module.utils import formatSize, decode -from module.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed -from module.lib.Getch import Getch -from module.lib.rename_process import renameProcess - -class Cli: - def __init__(self, client, command): - self.client = client - self.command = command - - if not self.command: - renameProcess('pyLoadCli') - self.getch = Getch() - self.input = "" - self.inputline = 0 - self.lastLowestLine = 0 - self.menuline = 0 - - self.lock = Lock() - - #processor funcions, these will be changed dynamically depending on control flow - self.headerHandler = self #the download status - self.bodyHandler = self #the menu section - self.inputHandler = self - - os.system("clear") - println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) - println(2, "") - - self.thread = RefreshThread(self) - self.thread.start() - - self.start() - else: - self.processCommand() - - def reset(self): - """ reset to initial main menu """ - self.input = "" - self.headerHandler = self.bodyHandler = self.inputHandler = self - - def start(self): - """ main loop. handle input """ - while True: - #inp = raw_input() - inp = self.getch.impl() - if ord(inp) == 3: - os.system("clear") - sys.exit() # ctrl + c - elif ord(inp) == 13: #enter - try: - self.lock.acquire() - self.inputHandler.onEnter(self.input) - - except Exception, e: - println(2, red(e)) - finally: - self.lock.release() - - elif ord(inp) == 127: - self.input = self.input[:-1] #backspace - try: - self.lock.acquire() - self.inputHandler.onBackSpace() - finally: - self.lock.release() - - elif ord(inp) == 27: #ugly symbol - pass - else: - self.input += inp - try: - self.lock.acquire() - self.inputHandler.onChar(inp) - finally: - self.lock.release() - - self.inputline = self.bodyHandler.renderBody(self.menuline) - self.renderFooter(self.inputline) - - - def refresh(self): - """refresh screen""" - - println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) - println(2, "") - - self.lock.acquire() - - self.menuline = self.headerHandler.renderHeader(3) + 1 - println(self.menuline - 1, "") - self.inputline = self.bodyHandler.renderBody(self.menuline) - self.renderFooter(self.inputline) - - self.lock.release() - - - def setInput(self, string=""): - self.input = string - - def setHandler(self, klass): - #create new handler with reference to cli - self.bodyHandler = self.inputHandler = klass(self) - self.input = "" - - def renderHeader(self, line): - """ prints download status """ - #print updated information - # print "\033[J" #clear screen - # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) - # self.println(2, "") - # self.println(3, white(_("%s Downloads:") % (len(data)))) - - data = self.client.statusDownloads() - speed = 0 - - println(line, white(_("%s Downloads:") % (len(data)))) - line += 1 - - for download in data: - if download.status == 12: # downloading - percent = download.percent - z = percent / 4 - speed += download.speed - println(line, cyan(download.name)) - line += 1 - println(line, - blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _( - " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green( - download.format_size) + _(" Finished in: ") + green(download.format_eta) + _( - " ID: ") + green(download.fid)) - line += 1 - if download.status == 5: - println(line, cyan(download.name)) - line += 1 - println(line, _("waiting: ") + green(download.format_wait)) - line += 1 - - println(line, "") - line += 1 - status = self.client.statusServer() - if status.pause: - paused = _("Status:") + " " + red(_("paused")) - else: - paused = _("Status:") + " " + red(_("running")) - - println(line,"%s %s: %s %s: %s %s: %s" % ( - paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red( - status.queue), _("Total"), red(status.total))) - - return line + 1 - - def renderBody(self, line): - """ prints initial menu """ - println(line, white(_("Menu:"))) - println(line + 1, "") - println(line + 2, mag("1.") + _(" Add Links")) - println(line + 3, mag("2.") + _(" Manage Queue")) - println(line + 4, mag("3.") + _(" Manage Collector")) - println(line + 5, mag("4.") + _(" (Un)Pause Server")) - println(line + 6, mag("5.") + _(" Kill Server")) - println(line + 7, mag("6.") + _(" Quit")) - - return line + 8 - - def renderFooter(self, line): - """ prints out the input line with input """ - println(line, "") - line += 1 - - println(line, white(" Input: ") + decode(self.input)) - - #clear old output - if line < self.lastLowestLine: - for i in range(line + 1, self.lastLowestLine + 1): - println(i, "") - - self.lastLowestLine = line - - #set cursor to position - print "\033[" + str(self.inputline) + ";0H" - - def onChar(self, char): - """ default no special handling for single chars """ - if char == "1": - self.setHandler(AddPackage) - elif char == "2": - self.setHandler(ManageFiles) - elif char == "3": - self.setHandler(ManageFiles) - self.bodyHandler.target = Destination.Collector - elif char == "4": - self.client.togglePause() - self.setInput() - elif char == "5": - self.client.kill() - self.client.close() - sys.exit() - elif char == "6": - os.system('clear') - sys.exit() - - def onEnter(self, inp): - pass - - def onBackSpace(self): - pass - - def processCommand(self): - command = self.command[0] - args = [] - if len(self.command) > 1: - args = self.command[1:] - - if command == "status": - files = self.client.statusDownloads() - - if not files: - print "No downloads running." - - for download in files: - if download.status == 12: # downloading - print print_status(download) - print "\tDownloading: %s @ %s/s\t %s (%s%%)" % ( - download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft), - download.percent) - elif download.status == 5: - print print_status(download) - print "\tWaiting: %s" % download.format_wait - else: - print print_status(download) - - elif command == "queue": - print_packages(self.client.getQueueData()) - - elif command == "collector": - print_packages(self.client.getCollectorData()) - - elif command == "add": - if len(args) < 2: - print _("Please use this syntax: add ...") - return - - self.client.addPackage(args[0], args[1:], Destination.Queue, "") - - elif command == "add_coll": - if len(args) < 2: - print _("Please use this syntax: add ...") - return - - self.client.addPackage(args[0], args[1:], Destination.Collector, "") - - elif command == "del_file": - self.client.deleteFiles([int(x) for x in args]) - print "Files deleted." - - elif command == "del_package": - self.client.deletePackages([int(x) for x in args]) - print "Packages deleted." - - elif command == "move": - for pid in args: - pack = self.client.getPackageInfo(int(pid)) - self.client.movePackage((pack.dest + 1) % 2, pack.pid) - - elif command == "check": - print _("Checking %d links:") % len(args) - print - rid = self.client.checkOnlineStatus(args).rid - self.printOnlineCheck(self.client, rid) - - - elif command == "check_container": - path = args[0] - if not exists(join(owd, path)): - print _("File does not exists.") - return - - f = open(join(owd, path), "rb") - content = f.read() - f.close() - - rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid - self.printOnlineCheck(self.client, rid) - - - elif command == "pause": - self.client.pause() - - elif command == "unpause": - self.client.unpause() - - elif command == "toggle": - self.client.togglePause() - - elif command == "kill": - self.client.kill() - elif command == "restart_file": - for x in args: - self.client.restartFile(int(x)) - print "Files restarted." - elif command == "restart_package": - for pid in args: - self.client.restartPackage(int(pid)) - print "Packages restarted." - - else: - print_commands() - - def printOnlineCheck(self, client, rid): - while True: - sleep(1) - result = client.pollResults(rid) - for url, status in result.data.iteritems(): - if status.status == 2: check = "Online" - elif status.status == 1: check = "Offline" - else: check = "Unknown" - - print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check) - - if result.rid == -1: break - - -class RefreshThread(Thread): - def __init__(self, cli): - Thread.__init__(self) - self.setDaemon(True) - self.cli = cli - - def run(self): - while True: - sleep(1) - try: - self.cli.refresh() - except ConnectionClosed: - os.system("clear") - print _("pyLoad was terminated") - _exit(0) - except Exception, e: - println(2, red(str(e))) - self.cli.reset() - print_exc() - - -def print_help(config): - print - print "pyLoadCli Copyright (c) 2008-2012 the pyLoad Team" - print - print "Usage: [python] pyLoadCli.py [options] [command]" - print - print "" - print "See pyLoadCli.py -c for a complete listing." - print - print "" - print " -i, --interactive", " Start in interactive mode" - print - print " -u, --username=", " " * 2, "Specify user name" - print " --pw=", " " * 2, "Password" - print " -a, --address=", " " * 3, "Use address (current=%s)" % config["addr"] - print " -p, --port", " " * 7, "Use port (current=%s)" % config["port"] - print - print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config["language"] - print " -h, --help", " " * 7, "Display this help text" - print " -c, --commands", " " * 3, "List all available commands" - print - - -def print_packages(data): - for pack in data: - print "Package %s (#%s):" % (pack.name, pack.pid) - for download in pack.links: - print "\t" + print_file(download) - print - - -def print_file(download): - return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % { - "id": download.fid, - "name": download.name, - "statusmsg": download.statusmsg, - "plugin": download.plugin - } - - -def print_status(download): - return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % { - "id": download.fid, - "name": download.name, - "statusmsg": download.statusmsg, - "size": download.format_size - } - - -def print_commands(): - commands = [("status", _("Prints server status")), - ("queue", _("Prints downloads in queue")), - ("collector", _("Prints downloads in collector")), - ("add ...", _("Adds package to queue")), - ("add_coll ...", _("Adds package to collector")), - ("del_file ...", _("Delete Files from Queue/Collector")), - ("del_package ...", _("Delete Packages from Queue/Collector")), - ("move ...", _("Move Packages from Queue to Collector or vice versa")), - ("restart_file ...", _("Restart files")), - ("restart_package ...", _("Restart packages")), - ("check ...", _("Check online status, works with local container")), - ("check_container path", _("Checks online status of a container file")), - ("pause", _("Pause the server")), - ("unpause", _("continue downloads")), - ("toggle", _("Toggle pause/unpause")), - ("kill", _("kill server")), ] - - print _("List of commands:") - print - for c in commands: - print "%-35s %s" % c - - -def writeConfig(opts): - try: - with open(join(homedir, ".pyloadcli"), "w") as cfgfile: - cfgfile.write("[cli]") - for opt in opts: - cfgfile.write("%s=%s\n" % (opt, opts[opt])) - except: - print _("Couldn't write user config file") - - -def main(): - config = {"addr": "127.0.0.1", "port": "7227", "language": "en"} - try: - config["language"] = os.environ["LANG"][0:2] - except: - pass - - if (not exists(join(pypath, "locale", config["language"]))) or config["language"] == "": - config["language"] = "en" - - configFile = ConfigParser.ConfigParser() - configFile.read(join(homedir, ".pyloadcli")) - - if configFile.has_section("cli"): - for opt in configFile.items("cli"): - config[opt[0]] = opt[1] - - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("pyLoadCli", join(pypath, "locale"), - languages=[config["language"],"en"],fallback=True) - translation.install(unicode=True) - - interactive = False - command = None - username = "" - password = "" - - shortOptions = 'iu:p:a:hcl:' - longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="] - - try: - opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions) - for option, params in opts: - if option in ("-i", "--interactive"): - interactive = True - elif option in ("-u", "--username"): - username = params - elif option in ("-a", "--address"): - config["addr"] = params - elif option in ("-p", "--port"): - config["port"] = params - elif option in ("-l", "--language"): - config["language"] = params - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("pyLoadCli", join(pypath, "locale"), - languages=[config["language"],"en"],fallback=True) - translation.install(unicode=True) - elif option in ("-h", "--help"): - print_help(config) - exit() - elif option in ("--pw"): - password = params - elif option in ("-c", "--comands"): - print_commands() - exit() - - except GetoptError: - print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:]) - print_help(config) - exit() - - if len(extraparams) >= 1: - command = extraparams - - client = False - - if interactive: - try: - client = ThriftClient(config["addr"], int(config["port"]), username, password) - except WrongLogin: - pass - except NoSSL: - print _("You need py-openssl to connect to this pyLoad Core.") - exit() - except NoConnection: - config["addr"] = False - config["port"] = False - - if not client: - if not config["addr"]: config["addr"] = raw_input(_("Address: ")) - if not config["port"]: config["port"] = raw_input(_("Port: ")) - if not username: username = raw_input(_("Username: ")) - if not password: - from getpass import getpass - - password = getpass(_("Password: ")) - - try: - client = ThriftClient(config["addr"], int(config["port"]), username, password) - except WrongLogin: - print _("Login data is wrong.") - except NoConnection: - print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], - "port": config["port"]}) - - else: - try: - client = ThriftClient(config["addr"], int(config["port"]), username, password) - except WrongLogin: - print _("Login data is wrong.") - except NoConnection: - print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], - "port": config["port"]}) - except NoSSL: - print _("You need py-openssl to connect to this pyLoad core.") - - if interactive and command: print _("Interactive mode ignored since you passed some commands.") - - if client: - writeConfig(config) - cli = Cli(client, command) - - -if __name__ == "__main__": - main() diff --git a/pyLoadCore.py b/pyLoadCore.py deleted file mode 100755 index 8f939b861..000000000 --- a/pyLoadCore.py +++ /dev/null @@ -1,673 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - Copyright(c) 2008-2013 pyLoad Team - http://www.pyload.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - Subjected to the terms and conditions in LICENSE - - @author: spoob - @author: sebnapi - @author: RaNaN - @author: mkaay - @version: v0.4.9 -""" -CURRENT_VERSION = '0.4.9.9-dev' - -import __builtin__ - -from getopt import getopt, GetoptError -from imp import find_module -import logging -import logging.handlers -import os -from os import _exit, execl, getcwd, remove, walk, chdir, close -import signal -import sys -from sys import argv, executable, exit -from time import time, sleep -from traceback import print_exc - -import locale -locale.locale_alias = locale.windows_locale = {} #save ~100kb ram, no known sideeffects for now - -import subprocess -subprocess.__doc__ = None # the module with the largest doc we are using - -from module import InitHomeDir -from module.AccountManager import AccountManager -from module.config.ConfigParser import ConfigParser -from module.config.ConfigManager import ConfigManager -from module.PluginManager import PluginManager -from module.interaction.EventManager import EventManager -from module.network.RequestFactory import RequestFactory -from module.web.ServerThread import WebServer -from module.Scheduler import Scheduler -from module.common.JsEngine import JsEngine -from module.remote.RemoteManager import RemoteManager - -import module.common.pylgettext as gettext -from module.utils import formatSize, get_console_encoding -from module.utils.fs import free_space, exists, makedirs, join, chmod - -from codecs import getwriter - -# test runner overwrites sys.stdout -if hasattr(sys.stdout, "encoding"): enc = get_console_encoding(sys.stdout.encoding) -else: enc = "utf8" - -sys._stdout = sys.stdout -sys.stdout = getwriter(enc)(sys.stdout, errors="replace") - -# TODO List -# - configurable auth system ldap/mysql -# - cron job like sheduler -# - plugin stack / multi decrypter -# - media plugin type -# - general progress info -# - content attribute for files / sync status -# - sync with disk content / file manager / nested packages -# - sync between pyload cores -# - new attributes (date|sync status) -# - embedded packages -# - would require new/modified link collector concept -# - pausable links/packages -# - toggable accounts -# - interaction manager -# - improve external scripts -# - make pyload undestructable to fail plugins -> see ConfigParser first - -class Core(object): - """pyLoad Core, one tool to rule them all... (the filehosters) :D""" - - def __init__(self): - self.doDebug = False - self.running = False - self.daemon = False - self.remote = True - self.pdb = None - self.arg_links = [] - self.pidfile = "pyload.pid" - self.deleteLinks = False # will delete links on startup - - if len(argv) > 1: - try: - options, args = getopt(argv[1:], 'vchdusqp:', - ["version", "clear", "clean", "help", "debug", "user", - "setup", "configdir=", "changedir", "daemon", - "quit", "status", "no-remote","pidfile="]) - - for option, argument in options: - if option in ("-v", "--version"): - print "pyLoad", CURRENT_VERSION - exit() - elif option in ("-p", "--pidfile"): - self.pidfile = argument - elif option == "--daemon": - self.daemon = True - elif option in ("-c", "--clear"): - self.deleteLinks = True - elif option in ("-h", "--help"): - self.print_help() - exit() - elif option in ("-d", "--debug"): - self.doDebug = True - elif option in ("-u", "--user"): - from module.Setup import Setup - - self.config = ConfigParser() - s = Setup(pypath, self.config) - s.set_user() - exit() - elif option in ("-s", "--setup"): - from module.Setup import Setup - - self.config = ConfigParser() - s = Setup(pypath, self.config) - s.start() - exit() - elif option == "--changedir": - from module.Setup import Setup - - self.config = ConfigParser() - s = Setup(pypath, self.config) - s.conf_path(True) - exit() - elif option in ("-q", "--quit"): - self.quitInstance() - exit() - elif option == "--status": - pid = self.isAlreadyRunning() - if self.isAlreadyRunning(): - print pid - exit(0) - else: - print "false" - exit(1) - elif option == "--clean": - self.cleanTree() - exit() - elif option == "--no-remote": - self.remote = False - - except GetoptError: - print 'Unknown Argument(s) "%s"' % " ".join(argv[1:]) - self.print_help() - exit() - - def print_help(self): - print "" - print "pyLoad v%s 2008-2013 the pyLoad Team" % CURRENT_VERSION - print "" - if sys.argv[0].endswith(".py"): - print "Usage: python pyLoadCore.py [options]" - else: - print "Usage: pyLoadCore [options]" - print "" - print "" - print " -v, --version", " " * 10, "Print version to terminal" - print " -c, --clear", " " * 12, "Delete all saved packages/links" - #print " -a, --add=", " " * 2, "Add the specified links" - print " -u, --user", " " * 13, "Manages users" - print " -d, --debug", " " * 12, "Enable debug mode" - print " -s, --setup", " " * 12, "Run setup assistant" - print " --configdir=", " " * 6, "Run with as configuration directory" - print " -p, --pidfile=", " " * 3, "Set pidfile to " - print " --changedir", " " * 12, "Change configuration directory permanently" - print " --daemon", " " * 15, "Daemonize after startup" - print " --no-remote", " " * 12, "Disable remote access (saves RAM)" - print " --status", " " * 15, "Display pid if running or False" - print " --clean", " " * 16, "Remove .pyc/.pyo files" - print " -q, --quit", " " * 13, "Quit a running pyLoad instance" - print " -h, --help", " " * 13, "Display this help screen" - print "" - - def toggle_pause(self): - if self.threadManager.pause: - self.threadManager.pause = False - return False - elif not self.threadManager.pause: - self.threadManager.pause = True - return True - - def quit(self, a, b): - self.shutdown() - self.log.info(_("Received Quit signal")) - _exit(1) - - def writePidFile(self): - self.deletePidFile() - pid = os.getpid() - f = open(self.pidfile, "wb") - f.write(str(pid)) - f.close() - chmod(self.pidfile, 0660) - - def deletePidFile(self): - if self.checkPidFile(): - self.log.debug("Deleting old pidfile %s" % self.pidfile) - os.remove(self.pidfile) - - def checkPidFile(self): - """ return pid as int or 0""" - if os.path.isfile(self.pidfile): - f = open(self.pidfile, "rb") - pid = f.read().strip() - f.close() - if pid: - pid = int(pid) - return pid - - return 0 - - def isAlreadyRunning(self): - pid = self.checkPidFile() - if not pid or os.name == "nt": return False - try: - os.kill(pid, 0) # 0 - default signal (does nothing) - except: - return 0 - - return pid - - def quitInstance(self): - if os.name == "nt": - print "Not supported on windows." - return - - pid = self.isAlreadyRunning() - if not pid: - print "No pyLoad running." - return - - try: - os.kill(pid, 3) #SIGUIT - - t = time() - print "waiting for pyLoad to quit" - - while exists(self.pidfile) and t + 10 > time(): - sleep(0.25) - - if not exists(self.pidfile): - print "pyLoad successfully stopped" - else: - os.kill(pid, 9) #SIGKILL - print "pyLoad did not respond" - print "Kill signal was send to process with id %s" % pid - - except: - print "Error quitting pyLoad" - - - def cleanTree(self): - for path, dirs, files in walk(self.path("")): - for f in files: - if not f.endswith(".pyo") and not f.endswith(".pyc"): - continue - - if "_25" in f or "_26" in f or "_27" in f: - continue - - print join(path, f) - remove(join(path, f)) - - def start(self, rpc=True, web=True, tests=False): - """ starts the fun :D """ - - self.version = CURRENT_VERSION - - # TODO: Re-enable when its working again - # TODO: Don't forget it - if False and not exists("pyload.conf") and not tests: - from module.Setup import Setup - - print "This is your first start, running configuration assistant now." - self.config = ConfigParser() - s = Setup(pypath, self.config) - res = False - try: - res = s.start() - except SystemExit: - pass - except KeyboardInterrupt: - print "\nSetup interrupted" - except: - res = False - print_exc() - print "Setup failed" - if not res: - remove("pyload.conf") - - exit() - - try: signal.signal(signal.SIGQUIT, self.quit) - except: pass - - self.config = ConfigParser() - - gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) - translation = gettext.translation("pyLoad", self.path("locale"), - languages=[self.config['general']['language'],"en"],fallback=True) - translation.install(True) - - # load again so translations are propagated - self.config.loadDefault() - - self.debug = self.doDebug or self.config['general']['debug_mode'] - self.remote &= self.config['remote']['activated'] - - pid = self.isAlreadyRunning() - # don't exit when in test runner - if pid and not tests: - print _("pyLoad already running with pid %s") % pid - exit() - - if os.name != "nt" and self.config["general"]["renice"]: - os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid())) - - if self.config["permission"]["change_group"]: - if os.name != "nt": - try: - from grp import getgrnam - - group = getgrnam(self.config["permission"]["group"]) - os.setgid(group[2]) - except Exception, e: - print _("Failed changing group: %s") % e - - if self.config["permission"]["change_user"]: - if os.name != "nt": - try: - from pwd import getpwnam - - user = getpwnam(self.config["permission"]["user"]) - os.setuid(user[2]) - except Exception, e: - print _("Failed changing user: %s") % e - - if self.debug: - self.init_logger(logging.DEBUG) # logging level - else: - self.init_logger(logging.INFO) # logging level - - self.do_kill = False - self.do_restart = False - self.shuttedDown = False - - self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION) - self.log.info(_("Using home directory: %s") % getcwd()) - - if not tests: - self.writePidFile() - - self.check_install("Crypto", _("pycrypto to decode container files")) - - self.captcha = True # checks seems to fail, although tesseract is available - - if self.config['ssl']['activated']: - self.check_install("OpenSSL", _("OpenSSL for secure connection")) - - self.eventManager = self.evm = EventManager(self) - self.setupDB() - - # Upgrade to configManager - self.config = ConfigManager(self, self.config) - - if self.deleteLinks: - self.log.info(_("All links removed")) - self.db.purgeLinks() - - self.requestFactory = RequestFactory(self) - __builtin__.pyreq = self.requestFactory - - # deferred import, could improve start-up time - from module import Api - from module.AddonManager import AddonManager - from module.interaction.InteractionManager import InteractionManager - from module.threads.ThreadManager import ThreadManager - - self.api = Api.Api(self) - - self.scheduler = Scheduler(self) - - #hell yeah, so many important managers :D - self.pluginManager = PluginManager(self) - self.interactionManager = self.im = InteractionManager(self) - self.accountManager = AccountManager(self) - self.threadManager = ThreadManager(self) - self.addonManager = AddonManager(self) - self.remoteManager = RemoteManager(self) - - self.js = JsEngine() - - # enough initialization for test cases - if tests: return - - self.log.info(_("Download time: %s") % self.api.isTimeDownload()) - - if rpc: - self.remoteManager.startBackends() - - if web: - self.init_webserver() - - dl_folder = self.config["general"]["download_folder"] - - if not exists(dl_folder): - makedirs(dl_folder) - - spaceLeft = free_space(dl_folder) - - self.log.info(_("Free space: %s") % formatSize(spaceLeft)) - - self.config.save() #save so config files gets filled - - link_file = join(pypath, "links.txt") - - if exists(link_file): - f = open(link_file, "rb") - if f.read().strip(): - self.api.addPackage("links.txt", [link_file], 1) - f.close() - - link_file = "links.txt" - if exists(link_file): - f = open(link_file, "rb") - if f.read().strip(): - self.api.addPackage("links.txt", [link_file], 1) - f.close() - - #self.scheduler.addJob(0, self.accountManager.getAccountInfos) - self.log.info(_("Activating Accounts...")) - self.accountManager.refreshAllAccounts() - - #restart failed - if self.config["download"]["restart_failed"]: - self.log.info(_("Restarting failed downloads...")) - self.api.restartFailed() - - self.threadManager.pause = False - self.running = True - - self.addonManager.activateAddons() - - self.log.info(_("pyLoad is up and running")) - self.eventManager.dispatchEvent("coreReady") - - #test api -# from module.common.APIExerciser import startApiExerciser -# startApiExerciser(self, 3) - - #some memory stats -# from guppy import hpy -# hp=hpy() -# print hp.heap() -# import objgraph -# objgraph.show_most_common_types(limit=30) -# import memdebug -# memdebug.start(8002) -# from meliae import scanner -# scanner.dump_all_objects(self.path('objs.json')) - - locals().clear() - - while True: - sleep(1.5) - if self.do_restart: - self.log.info(_("restarting pyLoad")) - self.restart() - if self.do_kill: - self.shutdown() - self.log.info(_("pyLoad quits")) - self.removeLogger() - _exit(0) #@TODO thrift blocks shutdown - - self.threadManager.work() - self.interactionManager.work() - self.scheduler.work() - - def setupDB(self): - from module.database import DatabaseBackend - from module.FileManager import FileManager - - self.db = DatabaseBackend(self) # the backend - self.db.setup() - - self.files = FileManager(self) - self.db.manager = self.files #ugly? - - def init_webserver(self): - if self.config['webinterface']['activated']: - self.webserver = WebServer(self) - self.webserver.start() - - def init_logger(self, level): - console = logging.StreamHandler(sys.stdout) - frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", "%d.%m.%Y %H:%M:%S") - console.setFormatter(frm) - self.log = logging.getLogger("log") # setable in config - - if not exists(self.config['log']['log_folder']): - makedirs(self.config['log']['log_folder'], 0700) - - if self.config['log']['file_log']: - if self.config['log']['log_rotate']: - file_handler = logging.handlers.RotatingFileHandler(join(self.config['log']['log_folder'], 'log.txt'), - maxBytes=self.config['log']['log_size'] * 1024, - backupCount=int(self.config['log']['log_count']), - encoding="utf8") - else: - file_handler = logging.FileHandler(join(self.config['log']['log_folder'], 'log.txt'), encoding="utf8") - - file_handler.setFormatter(frm) - self.log.addHandler(file_handler) - - self.log.addHandler(console) #if console logging - self.log.setLevel(level) - - def removeLogger(self): - for h in list(self.log.handlers): - self.log.removeHandler(h) - h.close() - - def check_install(self, check_name, legend, python=True, essential=False): - """check whether needed tools are installed""" - try: - if python: - find_module(check_name) - else: - pipe = subprocess.PIPE - subprocess.Popen(check_name, stdout=pipe, stderr=pipe) - - return True - except: - if essential: - self.log.info(_("Install %s") % legend) - exit() - - return False - - def restart(self): - self.shutdown() - chdir(owd) - # close some open fds - for i in range(3,50): - try: - close(i) - except : - pass - - execl(executable, executable, *sys.argv) - _exit(0) - - def shutdown(self): - self.log.info(_("shutting down...")) - self.eventManager.dispatchEvent("coreShutdown") - try: - if self.config['webinterface']['activated'] and hasattr(self, "webserver"): - pass # TODO: quit webserver? -# self.webserver.quit() - - for thread in self.threadManager.threads: - thread.put("quit") - - self.api.stopAllDownloads() - self.addonManager.deactivateAddons() - - except: - self.print_exc() - self.log.info(_("error while shutting down")) - - finally: - self.files.syncSave() - self.db.shutdown() - self.shuttedDown = True - - self.deletePidFile() - - def shell(self): - """ stop and open an ipython shell inplace""" - if self.debug: - from IPython import embed - sys.stdout = sys._stdout - embed() - - def breakpoint(self): - if self.debug: - from IPython.core.debugger import Pdb - sys.stdout = sys._stdout - if not self.pdb: self.pdb = Pdb() - self.pdb.set_trace() - - def print_exc(self): - if self.debug: - print_exc() - - def path(self, *args): - return join(pypath, *args) - -def deamon(): - try: - pid = os.fork() - if pid > 0: - sys.exit(0) - except OSError, e: - print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) - sys.exit(1) - - # decouple from parent environment - os.setsid() - os.umask(0) - - # do second fork - try: - pid = os.fork() - if pid > 0: - # exit from second parent, print eventual PID before - print "Daemon PID %d" % pid - sys.exit(0) - except OSError, e: - print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) - sys.exit(1) - - # Iterate through and close some file descriptors. - for fd in range(0, 3): - try: - os.close(fd) - except OSError: # ERROR, fd wasn't open to begin with (ignored) - pass - - os.open(os.devnull, os.O_RDWR) # standard input (0) - os.dup2(0, 1) # standard output (1) - os.dup2(0, 2) - - pyload_core = Core() - pyload_core.start() - - -def main(): - #change name to 'pyLoadCore' - #from module.lib.rename_process import renameProcess - #renameProcess('pyLoadCore') - if "--daemon" in sys.argv: - deamon() - else: - pyload_core = Core() - try: - pyload_core.start() - except KeyboardInterrupt: - pyload_core.shutdown() - pyload_core.log.info(_("killed pyLoad from terminal")) - pyload_core.removeLogger() - _exit(1) - -# And so it begins... -if __name__ == "__main__": - main() - diff --git a/pyload-cli.py b/pyload-cli.py new file mode 100755 index 000000000..cf8fabd1a --- /dev/null +++ b/pyload-cli.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2012 RaNaN +# +#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 . +# +### +from __future__ import with_statement +from getopt import GetoptError, getopt + +import module.common.pylgettext as gettext +import os +from os import _exit +from os.path import join, exists, abspath, basename +import sys +from sys import exit +from threading import Thread, Lock +from time import sleep +from traceback import print_exc + +import ConfigParser + +from codecs import getwriter + +if os.name == "nt": + enc = "cp850" +else: + enc = "utf8" + +sys.stdout = getwriter(enc)(sys.stdout, errors="replace") + +from module import InitHomeDir +from module.cli.printer import * +from module.cli import AddPackage, ManageFiles + +from module.Api import Destination +from module.utils import formatSize, decode +from module.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed +from module.lib.Getch import Getch +from module.lib.rename_process import renameProcess + +class Cli: + def __init__(self, client, command): + self.client = client + self.command = command + + if not self.command: + renameProcess('pyLoadCli') + self.getch = Getch() + self.input = "" + self.inputline = 0 + self.lastLowestLine = 0 + self.menuline = 0 + + self.lock = Lock() + + #processor funcions, these will be changed dynamically depending on control flow + self.headerHandler = self #the download status + self.bodyHandler = self #the menu section + self.inputHandler = self + + os.system("clear") + println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + println(2, "") + + self.thread = RefreshThread(self) + self.thread.start() + + self.start() + else: + self.processCommand() + + def reset(self): + """ reset to initial main menu """ + self.input = "" + self.headerHandler = self.bodyHandler = self.inputHandler = self + + def start(self): + """ main loop. handle input """ + while True: + #inp = raw_input() + inp = self.getch.impl() + if ord(inp) == 3: + os.system("clear") + sys.exit() # ctrl + c + elif ord(inp) == 13: #enter + try: + self.lock.acquire() + self.inputHandler.onEnter(self.input) + + except Exception, e: + println(2, red(e)) + finally: + self.lock.release() + + elif ord(inp) == 127: + self.input = self.input[:-1] #backspace + try: + self.lock.acquire() + self.inputHandler.onBackSpace() + finally: + self.lock.release() + + elif ord(inp) == 27: #ugly symbol + pass + else: + self.input += inp + try: + self.lock.acquire() + self.inputHandler.onChar(inp) + finally: + self.lock.release() + + self.inputline = self.bodyHandler.renderBody(self.menuline) + self.renderFooter(self.inputline) + + + def refresh(self): + """refresh screen""" + + println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + println(2, "") + + self.lock.acquire() + + self.menuline = self.headerHandler.renderHeader(3) + 1 + println(self.menuline - 1, "") + self.inputline = self.bodyHandler.renderBody(self.menuline) + self.renderFooter(self.inputline) + + self.lock.release() + + + def setInput(self, string=""): + self.input = string + + def setHandler(self, klass): + #create new handler with reference to cli + self.bodyHandler = self.inputHandler = klass(self) + self.input = "" + + def renderHeader(self, line): + """ prints download status """ + #print updated information + # print "\033[J" #clear screen + # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + # self.println(2, "") + # self.println(3, white(_("%s Downloads:") % (len(data)))) + + data = self.client.statusDownloads() + speed = 0 + + println(line, white(_("%s Downloads:") % (len(data)))) + line += 1 + + for download in data: + if download.status == 12: # downloading + percent = download.percent + z = percent / 4 + speed += download.speed + println(line, cyan(download.name)) + line += 1 + println(line, + blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _( + " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green( + download.format_size) + _(" Finished in: ") + green(download.format_eta) + _( + " ID: ") + green(download.fid)) + line += 1 + if download.status == 5: + println(line, cyan(download.name)) + line += 1 + println(line, _("waiting: ") + green(download.format_wait)) + line += 1 + + println(line, "") + line += 1 + status = self.client.statusServer() + if status.pause: + paused = _("Status:") + " " + red(_("paused")) + else: + paused = _("Status:") + " " + red(_("running")) + + println(line,"%s %s: %s %s: %s %s: %s" % ( + paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red( + status.queue), _("Total"), red(status.total))) + + return line + 1 + + def renderBody(self, line): + """ prints initial menu """ + println(line, white(_("Menu:"))) + println(line + 1, "") + println(line + 2, mag("1.") + _(" Add Links")) + println(line + 3, mag("2.") + _(" Manage Queue")) + println(line + 4, mag("3.") + _(" Manage Collector")) + println(line + 5, mag("4.") + _(" (Un)Pause Server")) + println(line + 6, mag("5.") + _(" Kill Server")) + println(line + 7, mag("6.") + _(" Quit")) + + return line + 8 + + def renderFooter(self, line): + """ prints out the input line with input """ + println(line, "") + line += 1 + + println(line, white(" Input: ") + decode(self.input)) + + #clear old output + if line < self.lastLowestLine: + for i in range(line + 1, self.lastLowestLine + 1): + println(i, "") + + self.lastLowestLine = line + + #set cursor to position + print "\033[" + str(self.inputline) + ";0H" + + def onChar(self, char): + """ default no special handling for single chars """ + if char == "1": + self.setHandler(AddPackage) + elif char == "2": + self.setHandler(ManageFiles) + elif char == "3": + self.setHandler(ManageFiles) + self.bodyHandler.target = Destination.Collector + elif char == "4": + self.client.togglePause() + self.setInput() + elif char == "5": + self.client.kill() + self.client.close() + sys.exit() + elif char == "6": + os.system('clear') + sys.exit() + + def onEnter(self, inp): + pass + + def onBackSpace(self): + pass + + def processCommand(self): + command = self.command[0] + args = [] + if len(self.command) > 1: + args = self.command[1:] + + if command == "status": + files = self.client.statusDownloads() + + if not files: + print "No downloads running." + + for download in files: + if download.status == 12: # downloading + print print_status(download) + print "\tDownloading: %s @ %s/s\t %s (%s%%)" % ( + download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft), + download.percent) + elif download.status == 5: + print print_status(download) + print "\tWaiting: %s" % download.format_wait + else: + print print_status(download) + + elif command == "queue": + print_packages(self.client.getQueueData()) + + elif command == "collector": + print_packages(self.client.getCollectorData()) + + elif command == "add": + if len(args) < 2: + print _("Please use this syntax: add ...") + return + + self.client.addPackage(args[0], args[1:], Destination.Queue, "") + + elif command == "add_coll": + if len(args) < 2: + print _("Please use this syntax: add ...") + return + + self.client.addPackage(args[0], args[1:], Destination.Collector, "") + + elif command == "del_file": + self.client.deleteFiles([int(x) for x in args]) + print "Files deleted." + + elif command == "del_package": + self.client.deletePackages([int(x) for x in args]) + print "Packages deleted." + + elif command == "move": + for pid in args: + pack = self.client.getPackageInfo(int(pid)) + self.client.movePackage((pack.dest + 1) % 2, pack.pid) + + elif command == "check": + print _("Checking %d links:") % len(args) + print + rid = self.client.checkOnlineStatus(args).rid + self.printOnlineCheck(self.client, rid) + + + elif command == "check_container": + path = args[0] + if not exists(join(owd, path)): + print _("File does not exists.") + return + + f = open(join(owd, path), "rb") + content = f.read() + f.close() + + rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid + self.printOnlineCheck(self.client, rid) + + + elif command == "pause": + self.client.pause() + + elif command == "unpause": + self.client.unpause() + + elif command == "toggle": + self.client.togglePause() + + elif command == "kill": + self.client.kill() + elif command == "restart_file": + for x in args: + self.client.restartFile(int(x)) + print "Files restarted." + elif command == "restart_package": + for pid in args: + self.client.restartPackage(int(pid)) + print "Packages restarted." + + else: + print_commands() + + def printOnlineCheck(self, client, rid): + while True: + sleep(1) + result = client.pollResults(rid) + for url, status in result.data.iteritems(): + if status.status == 2: check = "Online" + elif status.status == 1: check = "Offline" + else: check = "Unknown" + + print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check) + + if result.rid == -1: break + + +class RefreshThread(Thread): + def __init__(self, cli): + Thread.__init__(self) + self.setDaemon(True) + self.cli = cli + + def run(self): + while True: + sleep(1) + try: + self.cli.refresh() + except ConnectionClosed: + os.system("clear") + print _("pyLoad was terminated") + _exit(0) + except Exception, e: + println(2, red(str(e))) + self.cli.reset() + print_exc() + + +def print_help(config): + print + print "pyLoadCli Copyright (c) 2008-2012 the pyLoad Team" + print + print "Usage: [python] pyLoadCli.py [options] [command]" + print + print "" + print "See pyLoadCli.py -c for a complete listing." + print + print "" + print " -i, --interactive", " Start in interactive mode" + print + print " -u, --username=", " " * 2, "Specify user name" + print " --pw=", " " * 2, "Password" + print " -a, --address=", " " * 3, "Use address (current=%s)" % config["addr"] + print " -p, --port", " " * 7, "Use port (current=%s)" % config["port"] + print + print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config["language"] + print " -h, --help", " " * 7, "Display this help text" + print " -c, --commands", " " * 3, "List all available commands" + print + + +def print_packages(data): + for pack in data: + print "Package %s (#%s):" % (pack.name, pack.pid) + for download in pack.links: + print "\t" + print_file(download) + print + + +def print_file(download): + return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % { + "id": download.fid, + "name": download.name, + "statusmsg": download.statusmsg, + "plugin": download.plugin + } + + +def print_status(download): + return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % { + "id": download.fid, + "name": download.name, + "statusmsg": download.statusmsg, + "size": download.format_size + } + + +def print_commands(): + commands = [("status", _("Prints server status")), + ("queue", _("Prints downloads in queue")), + ("collector", _("Prints downloads in collector")), + ("add ...", _("Adds package to queue")), + ("add_coll ...", _("Adds package to collector")), + ("del_file ...", _("Delete Files from Queue/Collector")), + ("del_package ...", _("Delete Packages from Queue/Collector")), + ("move ...", _("Move Packages from Queue to Collector or vice versa")), + ("restart_file ...", _("Restart files")), + ("restart_package ...", _("Restart packages")), + ("check ...", _("Check online status, works with local container")), + ("check_container path", _("Checks online status of a container file")), + ("pause", _("Pause the server")), + ("unpause", _("continue downloads")), + ("toggle", _("Toggle pause/unpause")), + ("kill", _("kill server")), ] + + print _("List of commands:") + print + for c in commands: + print "%-35s %s" % c + + +def writeConfig(opts): + try: + with open(join(homedir, ".pyloadcli"), "w") as cfgfile: + cfgfile.write("[cli]") + for opt in opts: + cfgfile.write("%s=%s\n" % (opt, opts[opt])) + except: + print _("Couldn't write user config file") + + +def main(): + config = {"addr": "127.0.0.1", "port": "7227", "language": "en"} + try: + config["language"] = os.environ["LANG"][0:2] + except: + pass + + if (not exists(join(pypath, "locale", config["language"]))) or config["language"] == "": + config["language"] = "en" + + configFile = ConfigParser.ConfigParser() + configFile.read(join(homedir, ".pyloadcli")) + + if configFile.has_section("cli"): + for opt in configFile.items("cli"): + config[opt[0]] = opt[1] + + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("pyLoadCli", join(pypath, "locale"), + languages=[config["language"],"en"],fallback=True) + translation.install(unicode=True) + + interactive = False + command = None + username = "" + password = "" + + shortOptions = 'iu:p:a:hcl:' + longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="] + + try: + opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions) + for option, params in opts: + if option in ("-i", "--interactive"): + interactive = True + elif option in ("-u", "--username"): + username = params + elif option in ("-a", "--address"): + config["addr"] = params + elif option in ("-p", "--port"): + config["port"] = params + elif option in ("-l", "--language"): + config["language"] = params + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("pyLoadCli", join(pypath, "locale"), + languages=[config["language"],"en"],fallback=True) + translation.install(unicode=True) + elif option in ("-h", "--help"): + print_help(config) + exit() + elif option in ("--pw"): + password = params + elif option in ("-c", "--comands"): + print_commands() + exit() + + except GetoptError: + print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:]) + print_help(config) + exit() + + if len(extraparams) >= 1: + command = extraparams + + client = False + + if interactive: + try: + client = ThriftClient(config["addr"], int(config["port"]), username, password) + except WrongLogin: + pass + except NoSSL: + print _("You need py-openssl to connect to this pyLoad Core.") + exit() + except NoConnection: + config["addr"] = False + config["port"] = False + + if not client: + if not config["addr"]: config["addr"] = raw_input(_("Address: ")) + if not config["port"]: config["port"] = raw_input(_("Port: ")) + if not username: username = raw_input(_("Username: ")) + if not password: + from getpass import getpass + + password = getpass(_("Password: ")) + + try: + client = ThriftClient(config["addr"], int(config["port"]), username, password) + except WrongLogin: + print _("Login data is wrong.") + except NoConnection: + print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], + "port": config["port"]}) + + else: + try: + client = ThriftClient(config["addr"], int(config["port"]), username, password) + except WrongLogin: + print _("Login data is wrong.") + except NoConnection: + print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"], + "port": config["port"]}) + except NoSSL: + print _("You need py-openssl to connect to this pyLoad core.") + + if interactive and command: print _("Interactive mode ignored since you passed some commands.") + + if client: + writeConfig(config) + cli = Cli(client, command) + + +if __name__ == "__main__": + main() diff --git a/pyload.py b/pyload.py new file mode 100755 index 000000000..865f29c8f --- /dev/null +++ b/pyload.py @@ -0,0 +1,673 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Copyright(c) 2008-2013 pyLoad Team + http://www.pyload.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Subjected to the terms and conditions in LICENSE + + @author: spoob + @author: sebnapi + @author: RaNaN + @author: mkaay + @version: v0.4.9 +""" +CURRENT_VERSION = '0.4.9.9-dev' + +import __builtin__ + +from getopt import getopt, GetoptError +from imp import find_module +import logging +import logging.handlers +import os +from os import _exit, execl, getcwd, remove, walk, chdir, close +import signal +import sys +from sys import argv, executable, exit +from time import time, sleep +from traceback import print_exc + +import locale +locale.locale_alias = locale.windows_locale = {} #save ~100kb ram, no known sideeffects for now + +import subprocess +subprocess.__doc__ = None # the module with the largest doc we are using + +from module import InitHomeDir +from module.AccountManager import AccountManager +from module.config.ConfigParser import ConfigParser +from module.config.ConfigManager import ConfigManager +from module.PluginManager import PluginManager +from module.interaction.EventManager import EventManager +from module.network.RequestFactory import RequestFactory +from module.web.ServerThread import WebServer +from module.Scheduler import Scheduler +from module.common.JsEngine import JsEngine +from module.remote.RemoteManager import RemoteManager + +import module.common.pylgettext as gettext +from module.utils import formatSize, get_console_encoding +from module.utils.fs import free_space, exists, makedirs, join, chmod + +from codecs import getwriter + +# test runner overwrites sys.stdout +if hasattr(sys.stdout, "encoding"): enc = get_console_encoding(sys.stdout.encoding) +else: enc = "utf8" + +sys._stdout = sys.stdout +sys.stdout = getwriter(enc)(sys.stdout, errors="replace") + +# TODO List +# - configurable auth system ldap/mysql +# - cron job like sheduler +# - plugin stack / multi decrypter +# - media plugin type +# - general progress info +# - content attribute for files / sync status +# - sync with disk content / file manager / nested packages +# - sync between pyload cores +# - new attributes (date|sync status) +# - embedded packages +# - would require new/modified link collector concept +# - pausable links/packages +# - toggable accounts +# - interaction manager +# - improve external scripts +# - make pyload undestructable to fail plugins -> see ConfigParser first + +class Core(object): + """pyLoad Core, one tool to rule them all... (the filehosters) :D""" + + def __init__(self): + self.doDebug = False + self.running = False + self.daemon = False + self.remote = True + self.pdb = None + self.arg_links = [] + self.pidfile = "pyload.pid" + self.deleteLinks = False # will delete links on startup + + if len(argv) > 1: + try: + options, args = getopt(argv[1:], 'vchdusqp:', + ["version", "clear", "clean", "help", "debug", "user", + "setup", "configdir=", "changedir", "daemon", + "quit", "status", "no-remote","pidfile="]) + + for option, argument in options: + if option in ("-v", "--version"): + print "pyLoad", CURRENT_VERSION + exit() + elif option in ("-p", "--pidfile"): + self.pidfile = argument + elif option == "--daemon": + self.daemon = True + elif option in ("-c", "--clear"): + self.deleteLinks = True + elif option in ("-h", "--help"): + self.print_help() + exit() + elif option in ("-d", "--debug"): + self.doDebug = True + elif option in ("-u", "--user"): + from module.Setup import Setup + + self.config = ConfigParser() + s = Setup(pypath, self.config) + s.set_user() + exit() + elif option in ("-s", "--setup"): + from module.Setup import Setup + + self.config = ConfigParser() + s = Setup(pypath, self.config) + s.start() + exit() + elif option == "--changedir": + from module.Setup import Setup + + self.config = ConfigParser() + s = Setup(pypath, self.config) + s.conf_path(True) + exit() + elif option in ("-q", "--quit"): + self.quitInstance() + exit() + elif option == "--status": + pid = self.isAlreadyRunning() + if self.isAlreadyRunning(): + print pid + exit(0) + else: + print "false" + exit(1) + elif option == "--clean": + self.cleanTree() + exit() + elif option == "--no-remote": + self.remote = False + + except GetoptError: + print 'Unknown Argument(s) "%s"' % " ".join(argv[1:]) + self.print_help() + exit() + + def print_help(self): + print "" + print "pyLoad v%s 2008-2013 the pyLoad Team" % CURRENT_VERSION + print "" + if sys.argv[0].endswith(".py"): + print "Usage: python pyload.py [options]" + else: + print "Usage: pyload [options]" + print "" + print "" + print " -v, --version", " " * 10, "Print version to terminal" + print " -c, --clear", " " * 12, "Delete all saved packages/links" + #print " -a, --add=", " " * 2, "Add the specified links" + print " -u, --user", " " * 13, "Manages users" + print " -d, --debug", " " * 12, "Enable debug mode" + print " -s, --setup", " " * 12, "Run setup assistant" + print " --configdir=", " " * 6, "Run with as configuration directory" + print " -p, --pidfile=", " " * 3, "Set pidfile to " + print " --changedir", " " * 12, "Change configuration directory permanently" + print " --daemon", " " * 15, "Daemonize after startup" + print " --no-remote", " " * 12, "Disable remote access (saves RAM)" + print " --status", " " * 15, "Display pid if running or False" + print " --clean", " " * 16, "Remove .pyc/.pyo files" + print " -q, --quit", " " * 13, "Quit a running pyLoad instance" + print " -h, --help", " " * 13, "Display this help screen" + print "" + + def toggle_pause(self): + if self.threadManager.pause: + self.threadManager.pause = False + return False + elif not self.threadManager.pause: + self.threadManager.pause = True + return True + + def quit(self, a, b): + self.shutdown() + self.log.info(_("Received Quit signal")) + _exit(1) + + def writePidFile(self): + self.deletePidFile() + pid = os.getpid() + f = open(self.pidfile, "wb") + f.write(str(pid)) + f.close() + chmod(self.pidfile, 0660) + + def deletePidFile(self): + if self.checkPidFile(): + self.log.debug("Deleting old pidfile %s" % self.pidfile) + os.remove(self.pidfile) + + def checkPidFile(self): + """ return pid as int or 0""" + if os.path.isfile(self.pidfile): + f = open(self.pidfile, "rb") + pid = f.read().strip() + f.close() + if pid: + pid = int(pid) + return pid + + return 0 + + def isAlreadyRunning(self): + pid = self.checkPidFile() + if not pid or os.name == "nt": return False + try: + os.kill(pid, 0) # 0 - default signal (does nothing) + except: + return 0 + + return pid + + def quitInstance(self): + if os.name == "nt": + print "Not supported on windows." + return + + pid = self.isAlreadyRunning() + if not pid: + print "No pyLoad running." + return + + try: + os.kill(pid, 3) #SIGUIT + + t = time() + print "waiting for pyLoad to quit" + + while exists(self.pidfile) and t + 10 > time(): + sleep(0.25) + + if not exists(self.pidfile): + print "pyLoad successfully stopped" + else: + os.kill(pid, 9) #SIGKILL + print "pyLoad did not respond" + print "Kill signal was send to process with id %s" % pid + + except: + print "Error quitting pyLoad" + + + def cleanTree(self): + for path, dirs, files in walk(self.path("")): + for f in files: + if not f.endswith(".pyo") and not f.endswith(".pyc"): + continue + + if "_25" in f or "_26" in f or "_27" in f: + continue + + print join(path, f) + remove(join(path, f)) + + def start(self, rpc=True, web=True, tests=False): + """ starts the fun :D """ + + self.version = CURRENT_VERSION + + # TODO: Re-enable when its working again + # TODO: Don't forget it + if False and not exists("pyload.conf") and not tests: + from module.Setup import Setup + + print "This is your first start, running configuration assistant now." + self.config = ConfigParser() + s = Setup(pypath, self.config) + res = False + try: + res = s.start() + except SystemExit: + pass + except KeyboardInterrupt: + print "\nSetup interrupted" + except: + res = False + print_exc() + print "Setup failed" + if not res: + remove("pyload.conf") + + exit() + + try: signal.signal(signal.SIGQUIT, self.quit) + except: pass + + self.config = ConfigParser() + + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("pyLoad", self.path("locale"), + languages=[self.config['general']['language'],"en"],fallback=True) + translation.install(True) + + # load again so translations are propagated + self.config.loadDefault() + + self.debug = self.doDebug or self.config['general']['debug_mode'] + self.remote &= self.config['remote']['activated'] + + pid = self.isAlreadyRunning() + # don't exit when in test runner + if pid and not tests: + print _("pyLoad already running with pid %s") % pid + exit() + + if os.name != "nt" and self.config["general"]["renice"]: + os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid())) + + if self.config["permission"]["change_group"]: + if os.name != "nt": + try: + from grp import getgrnam + + group = getgrnam(self.config["permission"]["group"]) + os.setgid(group[2]) + except Exception, e: + print _("Failed changing group: %s") % e + + if self.config["permission"]["change_user"]: + if os.name != "nt": + try: + from pwd import getpwnam + + user = getpwnam(self.config["permission"]["user"]) + os.setuid(user[2]) + except Exception, e: + print _("Failed changing user: %s") % e + + if self.debug: + self.init_logger(logging.DEBUG) # logging level + else: + self.init_logger(logging.INFO) # logging level + + self.do_kill = False + self.do_restart = False + self.shuttedDown = False + + self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION) + self.log.info(_("Using home directory: %s") % getcwd()) + + if not tests: + self.writePidFile() + + self.check_install("Crypto", _("pycrypto to decode container files")) + + self.captcha = True # checks seems to fail, although tesseract is available + + if self.config['ssl']['activated']: + self.check_install("OpenSSL", _("OpenSSL for secure connection")) + + self.eventManager = self.evm = EventManager(self) + self.setupDB() + + # Upgrade to configManager + self.config = ConfigManager(self, self.config) + + if self.deleteLinks: + self.log.info(_("All links removed")) + self.db.purgeLinks() + + self.requestFactory = RequestFactory(self) + __builtin__.pyreq = self.requestFactory + + # deferred import, could improve start-up time + from module import Api + from module.AddonManager import AddonManager + from module.interaction.InteractionManager import InteractionManager + from module.threads.ThreadManager import ThreadManager + + self.api = Api.Api(self) + + self.scheduler = Scheduler(self) + + #hell yeah, so many important managers :D + self.pluginManager = PluginManager(self) + self.interactionManager = self.im = InteractionManager(self) + self.accountManager = AccountManager(self) + self.threadManager = ThreadManager(self) + self.addonManager = AddonManager(self) + self.remoteManager = RemoteManager(self) + + self.js = JsEngine() + + # enough initialization for test cases + if tests: return + + self.log.info(_("Download time: %s") % self.api.isTimeDownload()) + + if rpc: + self.remoteManager.startBackends() + + if web: + self.init_webserver() + + dl_folder = self.config["general"]["download_folder"] + + if not exists(dl_folder): + makedirs(dl_folder) + + spaceLeft = free_space(dl_folder) + + self.log.info(_("Free space: %s") % formatSize(spaceLeft)) + + self.config.save() #save so config files gets filled + + link_file = join(pypath, "links.txt") + + if exists(link_file): + f = open(link_file, "rb") + if f.read().strip(): + self.api.addPackage("links.txt", [link_file], 1) + f.close() + + link_file = "links.txt" + if exists(link_file): + f = open(link_file, "rb") + if f.read().strip(): + self.api.addPackage("links.txt", [link_file], 1) + f.close() + + #self.scheduler.addJob(0, self.accountManager.getAccountInfos) + self.log.info(_("Activating Accounts...")) + self.accountManager.refreshAllAccounts() + + #restart failed + if self.config["download"]["restart_failed"]: + self.log.info(_("Restarting failed downloads...")) + self.api.restartFailed() + + self.threadManager.pause = False + self.running = True + + self.addonManager.activateAddons() + + self.log.info(_("pyLoad is up and running")) + self.eventManager.dispatchEvent("coreReady") + + #test api +# from module.common.APIExerciser import startApiExerciser +# startApiExerciser(self, 3) + + #some memory stats +# from guppy import hpy +# hp=hpy() +# print hp.heap() +# import objgraph +# objgraph.show_most_common_types(limit=30) +# import memdebug +# memdebug.start(8002) +# from meliae import scanner +# scanner.dump_all_objects(self.path('objs.json')) + + locals().clear() + + while True: + sleep(1.5) + if self.do_restart: + self.log.info(_("restarting pyLoad")) + self.restart() + if self.do_kill: + self.shutdown() + self.log.info(_("pyLoad quits")) + self.removeLogger() + _exit(0) #@TODO thrift blocks shutdown + + self.threadManager.work() + self.interactionManager.work() + self.scheduler.work() + + def setupDB(self): + from module.database import DatabaseBackend + from module.FileManager import FileManager + + self.db = DatabaseBackend(self) # the backend + self.db.setup() + + self.files = FileManager(self) + self.db.manager = self.files #ugly? + + def init_webserver(self): + if self.config['webinterface']['activated']: + self.webserver = WebServer(self) + self.webserver.start() + + def init_logger(self, level): + console = logging.StreamHandler(sys.stdout) + frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", "%d.%m.%Y %H:%M:%S") + console.setFormatter(frm) + self.log = logging.getLogger("log") # setable in config + + if not exists(self.config['log']['log_folder']): + makedirs(self.config['log']['log_folder'], 0700) + + if self.config['log']['file_log']: + if self.config['log']['log_rotate']: + file_handler = logging.handlers.RotatingFileHandler(join(self.config['log']['log_folder'], 'log.txt'), + maxBytes=self.config['log']['log_size'] * 1024, + backupCount=int(self.config['log']['log_count']), + encoding="utf8") + else: + file_handler = logging.FileHandler(join(self.config['log']['log_folder'], 'log.txt'), encoding="utf8") + + file_handler.setFormatter(frm) + self.log.addHandler(file_handler) + + self.log.addHandler(console) #if console logging + self.log.setLevel(level) + + def removeLogger(self): + for h in list(self.log.handlers): + self.log.removeHandler(h) + h.close() + + def check_install(self, check_name, legend, python=True, essential=False): + """check whether needed tools are installed""" + try: + if python: + find_module(check_name) + else: + pipe = subprocess.PIPE + subprocess.Popen(check_name, stdout=pipe, stderr=pipe) + + return True + except: + if essential: + self.log.info(_("Install %s") % legend) + exit() + + return False + + def restart(self): + self.shutdown() + chdir(owd) + # close some open fds + for i in range(3,50): + try: + close(i) + except : + pass + + execl(executable, executable, *sys.argv) + _exit(0) + + def shutdown(self): + self.log.info(_("shutting down...")) + self.eventManager.dispatchEvent("coreShutdown") + try: + if self.config['webinterface']['activated'] and hasattr(self, "webserver"): + pass # TODO: quit webserver? +# self.webserver.quit() + + for thread in self.threadManager.threads: + thread.put("quit") + + self.api.stopAllDownloads() + self.addonManager.deactivateAddons() + + except: + self.print_exc() + self.log.info(_("error while shutting down")) + + finally: + self.files.syncSave() + self.db.shutdown() + self.shuttedDown = True + + self.deletePidFile() + + def shell(self): + """ stop and open an ipython shell inplace""" + if self.debug: + from IPython import embed + sys.stdout = sys._stdout + embed() + + def breakpoint(self): + if self.debug: + from IPython.core.debugger import Pdb + sys.stdout = sys._stdout + if not self.pdb: self.pdb = Pdb() + self.pdb.set_trace() + + def print_exc(self): + if self.debug: + print_exc() + + def path(self, *args): + return join(pypath, *args) + +def deamon(): + try: + pid = os.fork() + if pid > 0: + sys.exit(0) + except OSError, e: + print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # decouple from parent environment + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent, print eventual PID before + print "Daemon PID %d" % pid + sys.exit(0) + except OSError, e: + print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # Iterate through and close some file descriptors. + for fd in range(0, 3): + try: + os.close(fd) + except OSError: # ERROR, fd wasn't open to begin with (ignored) + pass + + os.open(os.devnull, os.O_RDWR) # standard input (0) + os.dup2(0, 1) # standard output (1) + os.dup2(0, 2) + + pyload_core = Core() + pyload_core.start() + + +def main(): + #change name to 'pyLoadCore' + #from module.lib.rename_process import renameProcess + #renameProcess('pyLoadCore') + if "--daemon" in sys.argv: + deamon() + else: + pyload_core = Core() + try: + pyload_core.start() + except KeyboardInterrupt: + pyload_core.shutdown() + pyload_core.log.info(_("killed pyLoad from terminal")) + pyload_core.removeLogger() + _exit(1) + +# And so it begins... +if __name__ == "__main__": + main() + -- cgit v1.2.3