summaryrefslogtreecommitdiffstats
path: root/pyload/Core.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyload/Core.py')
-rwxr-xr-xpyload/Core.py686
1 files changed, 686 insertions, 0 deletions
diff --git a/pyload/Core.py b/pyload/Core.py
new file mode 100755
index 000000000..ad4412a87
--- /dev/null
+++ b/pyload/Core.py
@@ -0,0 +1,686 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay, sebnapi, spoob, vuolter
+# @version: v0.4.10
+
+from __future__ import with_statement
+
+import pyload
+import __builtin__
+
+from getopt import getopt, GetoptError
+import pyload.utils.pylgettext as gettext
+from imp import find_module
+import logging
+import logging.handlers
+import os
+from os import _exit, execl, getcwd, makedirs, remove, sep, walk, chdir, close
+from os.path import exists, join
+import signal
+import subprocess
+import sys
+from sys import argv, executable, exit
+from time import time, sleep
+from traceback import print_exc
+
+from pyload.manager.Account import AccountManager
+from pyload.manager.Captcha import CaptchaManager
+from pyload.config.Parser import ConfigParser
+from pyload.manager.Plugin import PluginManager
+from pyload.manager.Event import PullManager
+from pyload.network.RequestFactory import RequestFactory
+from pyload.manager.thread.Server import WebServer
+from pyload.manager.event.Scheduler import Scheduler
+from pyload.network.JsEngine import JsEngine
+from pyload import remote
+from pyload.manager.Remote import RemoteManager
+from pyload.database import DatabaseBackend, FileHandler
+
+from pyload.utils import freeSpace, formatSize, get_console_encoding
+
+from codecs import getwriter
+
+enc = get_console_encoding(sys.stdout.encoding)
+sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
+
+
+# TODO List
+# - configurable auth system ldap/mysql
+# - cron job like sheduler
+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.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", pyload.__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 pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ s.set_user()
+ exit()
+ elif option in ("-s", "--setup"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ s.start()
+ exit()
+ elif option == "--changedir":
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(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-2015 the pyLoad Team" % pyload.__version__
+ print
+ if sys.argv[0].endswith(".py"):
+ print "Usage: python pyload.py [options]"
+ else:
+ print "Usage: pyload [options]"
+ print
+ print "<Options>"
+ print " -v, --version", " " * 10, "Print version to terminal"
+ print " -c, --clear", " " * 12, "Delete all saved packages/links"
+ # print " -a, --add=<link/list>", " " * 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=<dir>", " " * 6, "Run with <dir> as config directory"
+ print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>"
+ print " --changedir", " " * 12, "Change config dir permanently"
+ print " --daemon", " " * 15, "Daemonmize after start"
+ 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 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()
+ with open(self.pidfile, "wb") as f:
+ f.write(str(pid))
+
+
+ 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):
+ with open(self.pidfile, "rb") as f:
+ pid = f.read().strip()
+ 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 Exception:
+ 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 Exception:
+ 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):
+ """ starts the fun :D """
+
+ self.version = pyload.__version__
+
+ if not exists("pyload.conf"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ print "This is your first start, running configuration assistent now."
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ res = False
+ try:
+ res = s.start()
+ except SystemExit:
+ pass
+ except KeyboardInterrupt:
+ print "\nSetup interrupted"
+ except Exception:
+ res = False
+ print_exc()
+ print "Setup failed"
+ if not res:
+ remove("pyload.conf")
+
+ exit()
+
+ try: signal.signal(signal.SIGQUIT, self.quit)
+ except Exception:
+ pass
+
+ self.config = ConfigParser()
+
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("pyLoad", self.path("locale"),
+ languages=[self.config.get("general", "language"), "en"], fallback=True)
+ translation.install(True)
+
+ self.debug = self.doDebug or self.config.get("general", "debug_mode")
+ self.remote &= self.config.get("remote", "activated")
+
+ pid = self.isAlreadyRunning()
+ if pid:
+ print _("pyLoad already running with pid %s") % pid
+ exit()
+
+ if os.name != "nt" and self.config.get("general", "renice"):
+ os.system("renice %d %d" % (self.config.get("general", "renice"), os.getpid()))
+
+ if self.config.get("permission", "change_group"):
+ if os.name != "nt":
+ try:
+ from grp import getgrnam
+
+ group = getgrnam(self.config.get("permission", "group"))
+ os.setgid(group[2])
+ except Exception, e:
+ print _("Failed changing group: %s") % e
+
+ if self.config.get("permission", "change_user"):
+ if os.name != "nt":
+ try:
+ from pwd import getpwnam
+
+ user = getpwnam(self.config.get("permission", "user"))
+ os.setuid(user[2])
+ except Exception, e:
+ print _("Failed changing user: %s") % e
+
+ self.check_file(self.config.get("log", "log_folder"), _("folder for logs"), True)
+
+ 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" % pyload.__version__)
+ self.log.info(_("Using home directory: %s") % getcwd())
+
+ self.writePidFile()
+
+ #@TODO refractor
+
+ remote.activated = self.remote
+ self.log.debug("Remote activated: %s" % self.remote)
+
+ self.check_install("Crypto", _("pycrypto to decode container files"))
+ # img = self.check_install("Image", _("Python Image Library (PIL) for captcha reading"))
+ # self.check_install("pycurl", _("pycurl to download any files"), True, True)
+ self.check_file("tmp", _("folder for temporary files"), True)
+ # tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if os.name != "nt" else True
+
+ self.captcha = True #: checks seems to fail, although tesseract is available
+
+ self.check_file(self.config.get("general", "download_folder"), _("folder for downloads"), True)
+
+ if self.config.get("ssl", "activated"):
+ self.check_install("OpenSSL", _("OpenSSL for secure connection"))
+
+ self.setupDB()
+ if self.config.oldRemoteData:
+ self.log.info(_("Moving old user config to DB"))
+ self.db.addUser(self.config.oldRemoteData['username'], self.config.oldRemoteData['password'])
+
+ self.log.info(_("Please check your logindata with ./pyload.py -u"))
+
+ if self.deleteLinks:
+ self.log.info(_("All links removed"))
+ self.db.purgeLinks()
+
+ self.requestFactory = RequestFactory(self)
+ __builtin__.pyreq = self.requestFactory
+
+ self.lastClientConnected = 0
+
+ # later imported because they would trigger api import, and remote value not set correctly
+ from pyload import api
+ from pyload.manager.Addon import AddonManager
+ from pyload.manager.Thread import ThreadManager
+
+ if api.activated != self.remote:
+ self.log.warning("Import error: API remote status not correct.")
+
+ self.api = api.Api(self)
+
+ self.scheduler = Scheduler(self)
+
+ # hell yeah, so many important managers :D
+ self.pluginManager = PluginManager(self)
+ self.pullManager = PullManager(self)
+ self.accountManager = AccountManager(self)
+ self.threadManager = ThreadManager(self)
+ self.captchaManager = CaptchaManager(self)
+ self.addonManager = AddonManager(self)
+ self.remoteManager = RemoteManager(self)
+
+ self.js = JsEngine(self)
+
+ self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload())
+
+ if rpc:
+ self.remoteManager.startBackends()
+
+ if web:
+ self.init_webserver()
+
+ spaceLeft = freeSpace(self.config.get("general", "download_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):
+ with open(link_file, "rb") as f:
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+
+ link_file = "links.txt"
+ if exists(link_file):
+ with open(link_file, "rb") as f:
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+
+ # self.scheduler.addJob(0, self.accountManager.getAccountInfos)
+ self.log.info(_("Activating Accounts..."))
+ self.accountManager.getAccountInfos()
+
+ self.threadManager.pause = False
+ self.running = True
+
+ self.log.info(_("Activating Plugins..."))
+ self.addonManager.coreReady()
+
+ self.log.info(_("pyLoad is up and running"))
+
+ locals().clear()
+
+ while True:
+ sleep(2)
+ 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.scheduler.work()
+
+
+ def setupDB(self):
+ self.db = DatabaseBackend(self) #: the backend
+ self.db.setup()
+
+ self.files = FileHandler(self)
+ self.db.manager = self.files #: ugly?
+
+
+ def init_webserver(self):
+ if self.config.get("webui", "activated"):
+ self.webserver = WebServer(self)
+ self.webserver.start()
+
+
+ def init_logger(self, level):
+ self.log = logging.getLogger("log")
+ self.log.setLevel(level)
+
+ date_fmt = "%Y-%m-%d %H:%M:%S"
+ fh_fmt = "%(asctime)s %(levelname)-8s %(message)s"
+
+ fh_frm = logging.Formatter(fh_fmt, date_fmt) #: file handler formatter
+ console_frm = fh_frm #: console formatter did not use colors as default
+
+ # Console formatter with colors
+ if self.config.get("log", "color_console"):
+ import colorlog
+
+ if self.config.get("log", "console_mode") == "label":
+ c_fmt = "%(asctime)s %(log_color)s%(bold)s%(white)s %(levelname)-8s%(reset)s %(message)s"
+ clr = {
+ 'DEBUG' : "bg_cyan" ,
+ 'INFO' : "bg_green" ,
+ 'WARNING' : "bg_yellow",
+ 'ERROR' : "bg_red" ,
+ 'CRITICAL': "bg_purple",
+ }
+
+ else:
+ c_fmt = "%(log_color)s%(asctime)s %(levelname)-8s %(message)s"
+ clr = {
+ 'DEBUG' : "cyan" ,
+ 'WARNING' : "yellow",
+ 'ERROR' : "red" ,
+ 'CRITICAL': "purple",
+ }
+
+ console_frm = colorlog.ColoredFormatter(c_fmt, date_fmt, clr)
+
+ # Set console formatter
+ console = logging.StreamHandler(sys.stdout)
+ console.setFormatter(console_frm)
+ self.log.addHandler(console)
+
+ log_folder = self.config.get("log", "log_folder")
+ if not exists(log_folder):
+ makedirs(log_folder, 0700)
+
+ # Set file handler formatter
+ if self.config.get("log", "file_log"):
+ if self.config.get("log", "log_rotate"):
+ file_handler = logging.handlers.RotatingFileHandler(join(log_folder, 'log.txt'),
+ maxBytes=self.config.get("log", "log_size") * 1024,
+ backupCount=int(self.config.get("log", "log_count")),
+ encoding="utf8")
+ else:
+ file_handler = logging.FileHandler(join(log_folder, 'log.txt'), encoding="utf8")
+
+ file_handler.setFormatter(fh_frm)
+ self.log.addHandler(file_handler)
+
+
+ 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 wether 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 Exception:
+ if essential:
+ self.log.info(_("Install %s") % legend)
+ exit()
+
+ return False
+
+
+ def check_file(self, check_names, description="", folder=False, empty=True, essential=False, quiet=False):
+ """check wether needed files exists"""
+ tmp_names = []
+ if not type(check_names) == list:
+ tmp_names.append(check_names)
+ else:
+ tmp_names.extend(check_names)
+ file_created = True
+ file_exists = True
+ for tmp_name in tmp_names:
+ if not exists(tmp_name):
+ file_exists = False
+ if empty:
+ try:
+ if folder:
+ tmp_name = tmp_name.replace("/", sep)
+ makedirs(tmp_name)
+ else:
+ open(tmp_name, "w")
+ except Exception:
+ file_created = False
+ else:
+ file_created = False
+
+ if not file_exists and not quiet:
+ if file_created:
+ # self.log.info( _("%s created") % description )
+ pass
+ else:
+ if not empty:
+ self.log.warning(
+ _("could not find %(desc)s: %(name)s") % {"desc": description, "name": tmp_name})
+ else:
+ print _("could not create %(desc)s: %(name)s") % {"desc": description, "name": tmp_name}
+ if essential:
+ exit()
+
+
+ def isClientConnected(self):
+ return (self.lastClientConnected + 30) > time()
+
+
+ def restart(self):
+ self.shutdown()
+ chdir(owd)
+ # close some open fds
+ for i in xrange(3, 50):
+ try:
+ close(i)
+ except Exception:
+ pass
+
+ execl(executable, executable, *sys.argv)
+ _exit(0)
+
+
+ def shutdown(self):
+ self.log.info(_("shutting down..."))
+ try:
+ if self.config.get("webui", "activated") and hasattr(self, "webserver"):
+ self.webserver.quit()
+
+ for thread in list(self.threadManager.threads):
+ thread.put("quit")
+ pyfiles = self.files.cache.values()
+
+ for pyfile in pyfiles:
+ pyfile.abortDownload()
+
+ self.addonManager.coreExiting()
+
+ except Exception:
+ if self.debug:
+ print_exc()
+ self.log.info(_("error while shutting down"))
+
+ finally:
+ self.files.syncSave()
+ self.shuttedDown = True
+
+ self.deletePidFile()
+
+
+ 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 xrange(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():
+ 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()