From 60ae0ee3dce4715621b9964c91ad96e08e123204 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 10 May 2011 21:23:46 +0200 Subject: rewritten cli, closed #275, #304 --- module/HookManager.py | 6 +- module/SafeEval.py | 70 ---------- module/Unzip.py | 50 -------- module/cli/AddPackage.py | 66 ++++++++++ module/cli/Handler.py | 48 +++++++ module/cli/ManageFiles.py | 204 ++++++++++++++++++++++++++++++ module/cli/__init__.py | 2 + module/cli/printer.py | 26 ++++ module/lib/Getch.py | 76 +++++++++++ module/lib/SafeEval.py | 70 ++++++++++ module/lib/Unzip.py | 50 ++++++++ module/plugins/PluginManager.py | 2 +- module/remote/thriftbackend/Handler.py | 1 + module/remote/thriftbackend/pyload.thrift | 2 +- 14 files changed, 547 insertions(+), 126 deletions(-) delete mode 100644 module/SafeEval.py delete mode 100644 module/Unzip.py create mode 100644 module/cli/AddPackage.py create mode 100644 module/cli/Handler.py create mode 100644 module/cli/ManageFiles.py create mode 100644 module/cli/__init__.py create mode 100644 module/cli/printer.py create mode 100644 module/lib/Getch.py create mode 100644 module/lib/SafeEval.py create mode 100644 module/lib/Unzip.py (limited to 'module') diff --git a/module/HookManager.py b/module/HookManager.py index c3da485a9..1138a4c29 100644 --- a/module/HookManager.py +++ b/module/HookManager.py @@ -62,18 +62,16 @@ class HookManager: def addRPC(self, plugin, func, doc): plugin = plugin.rpartition(".")[2] - doc = doc.strip() + doc = doc.strip() if doc else "" if self.methods.has_key(plugin): self.methods[plugin][func] = doc else: self.methods[plugin] = {func: doc} - print self.methods - def callRPC(self, plugin, func, args, parse): if not args: args = tuple() - if parse is not False: + if parse: args = tuple([literal_eval(x) for x in args]) plugin = self.pluginMap[plugin] diff --git a/module/SafeEval.py b/module/SafeEval.py deleted file mode 100644 index 8ec9766e6..000000000 --- a/module/SafeEval.py +++ /dev/null @@ -1,70 +0,0 @@ -## {{{ http://code.activestate.com/recipes/364469/ (r2) -import compiler - -class Unsafe_Source_Error(Exception): - def __init__(self,error,descr = None,node = None): - self.error = error - self.descr = descr - self.node = node - self.lineno = getattr(node,"lineno",None) - - def __repr__(self): - return "Line %d. %s: %s" % (self.lineno, self.error, self.descr) - __str__ = __repr__ - -class SafeEval(object): - - def visit(self, node,**kw): - cls = node.__class__ - meth = getattr(self,'visit'+cls.__name__,self.default) - return meth(node, **kw) - - def default(self, node, **kw): - for child in node.getChildNodes(): - return self.visit(child, **kw) - - visitExpression = default - - def visitConst(self, node, **kw): - return node.value - - def visitDict(self,node,**kw): - return dict([(self.visit(k),self.visit(v)) for k,v in node.items]) - - def visitTuple(self,node, **kw): - return tuple(self.visit(i) for i in node.nodes) - - def visitList(self,node, **kw): - return [self.visit(i) for i in node.nodes] - -class SafeEvalWithErrors(SafeEval): - - def default(self, node, **kw): - raise Unsafe_Source_Error("Unsupported source construct", - node.__class__,node) - - def visitName(self,node, **kw): - if node.name == "None": - return None - elif node.name == "True": - return True - elif node.name == "False": - return False - else: - raise Unsafe_Source_Error("Strings must be quoted", - node.name, node) - - # Add more specific errors if desired - - -def safe_eval(source, fail_on_error = True): - walker = fail_on_error and SafeEvalWithErrors() or SafeEval() - try: - ast = compiler.parse(source,"eval") - except SyntaxError, err: - raise - try: - return walker.visit(ast) - except Unsafe_Source_Error, err: - raise -## end of http://code.activestate.com/recipes/364469/ }}} diff --git a/module/Unzip.py b/module/Unzip.py deleted file mode 100644 index f56fbe751..000000000 --- a/module/Unzip.py +++ /dev/null @@ -1,50 +0,0 @@ -import zipfile -import os - -class Unzip: - def __init__(self): - pass - - def extract(self, file, dir): - if not dir.endswith(':') and not os.path.exists(dir): - os.mkdir(dir) - - zf = zipfile.ZipFile(file) - - # create directory structure to house files - self._createstructure(file, dir) - - # extract files to directory structure - for i, name in enumerate(zf.namelist()): - - if not name.endswith('/') and not name.endswith("config"): - print "extracting", name.replace("pyload/","") - outfile = open(os.path.join(dir, name.replace("pyload/","")), 'wb') - outfile.write(zf.read(name)) - outfile.flush() - outfile.close() - - def _createstructure(self, file, dir): - self._makedirs(self._listdirs(file), dir) - - def _makedirs(self, directories, basedir): - """ Create any directories that don't currently exist """ - for dir in directories: - curdir = os.path.join(basedir, dir) - if not os.path.exists(curdir): - os.mkdir(curdir) - - def _listdirs(self, file): - """ Grabs all the directories in the zip structure - This is necessary to create the structure before trying - to extract the file to it. """ - zf = zipfile.ZipFile(file) - - dirs = [] - - for name in zf.namelist(): - if name.endswith('/'): - dirs.append(name.replace("pyload/","")) - - dirs.sort() - return dirs diff --git a/module/cli/AddPackage.py b/module/cli/AddPackage.py new file mode 100644 index 000000000..ded08742b --- /dev/null +++ b/module/cli/AddPackage.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2011 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 Handler import Handler +from printer import * + +class AddPackage(Handler): + """ let the user add packages """ + + def init(self): + self.name = "" + self.urls = [] + + def onEnter(self, inp): + if inp == "0": + self.cli.reset() + + if not self.name: + self.name = inp + self.setInput() + elif inp == "END": + #add package + self.client.addPackage(self.name, self.urls, 0) + self.cli.reset() + else: + if inp.strip(): + self.urls.append(inp) + self.setInput() + + def renderBody(self, line): + println(line, white(_("Add Package:"))) + println(line + 1, "") + line += 2 + + if not self.name: + println(line, _("Enter a name for the new package")) + println(line + 1, "") + line += 2 + else: + println(line, _("Package: %s") % self.name) + println(line + 1, _("Parse the links you want to add.")) + println(line + 2, _("Type %s when done.") % mag("END")) + println(line + 3, _("Links added: ") + mag(len(self.urls))) + line += 4 + + println(line, "") + println(line + 1, mag("0.") + _(" back to main menu")) + + return line + 2 \ No newline at end of file diff --git a/module/cli/Handler.py b/module/cli/Handler.py new file mode 100644 index 000000000..476d09386 --- /dev/null +++ b/module/cli/Handler.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2011 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 . +# +### +class Handler: + def __init__(self, cli): + self.cli = cli + self.init() + + client = property(lambda self: self.cli.client) + input = property(lambda self: self.cli.input) + + def init(self): + pass + + def onChar(self, char): + pass + + def onBackSpace(self): + pass + + def onEnter(self, inp): + pass + + def setInput(self, inp=""): + self.cli.setInput(inp) + + def backspace(self): + self.cli.setInput(self.input[:-1]) + + def renderBody(self, line): + """ gets the line where to render output and should return the line number below its content """ + return line + 1 \ No newline at end of file diff --git a/module/cli/ManageFiles.py b/module/cli/ManageFiles.py new file mode 100644 index 000000000..1d04c4c49 --- /dev/null +++ b/module/cli/ManageFiles.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2011 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 itertools import islice +from time import time + +from Handler import Handler +from printer import * + +from module.remote.thriftbackend.thriftgen.pyload.Pyload import PackageData, PackageDoesNotExists + +class ManageFiles(Handler): + """ possibility to manage queue/collector """ + + def init(self): + self.target = 1 #queue + self.pos = 0 #position in queue + self.package = -1 #choosen package + self.mode = "" # move/delete/restart + + self.cache = None + self.links = None + self.time = 0 + + def onChar(self, char): + if char in ("m", "d", "r"): + self.mode = char + self.setInput() + elif char == "p": + self.pos = max(0, self.pos - 5) + self.backspace() + elif char == "n": + self.pos += 5 + self.backspace() + + def onBackSpace(self): + if not self.input and self.mode: + self.mode = "" + if not self.input and self.package > -1: + self.package = -1 + + def onEnter(self, input): + if input == "0": + self.cli.reset() + elif self.package < 0 and self.mode: + #mode select + packs = self.parseInput(input) + if self.mode == "m": + [self.client.movePackage((self.target + 1) % 2, x) for x in packs] + elif self.mode == "d": + self.client.deletePackages(packs) + elif self.mode == "r": + [self.client.restartPackage(x) for x in packs] + + elif self.mode: + #edit links + links = self.parseInput(input, False) + + if self.mode == "d": + self.client.deleteFiles(links) + elif self.mode == "r": + map(self.client.restartFile, links) + + else: + #look into package + try: + self.package = int(input) + except: + pass + + self.cache = None + self.links = None + self.pos = 0 + self.mode = "" + self.setInput() + + + def renderBody(self, line): + if self.package < 0: + println(line, white(_("Manage Packages:"))) + else: + println(line, white((_("Manage Links:")))) + line += 1 + + if self.mode: + if self.mode == "m": + println(line, _("What do you want to move?")) + elif self.mode == "d": + println(line, _("What do you want to delete?")) + elif self.mode == "r": + println(line, _("What do you want to restart?")) + + println(line + 1, "Enter single number, comma seperated numbers or ranges. eg. 1,2,3 or 1-3.") + line += 2 + else: + println(line, _("Choose what yout want to do or enter package number.")) + println(line + 1, ("%s - %%s, %s - %%s, %s - %%s" % (mag("d"), mag("m"), mag("r"))) % ( + _("delete"), _("move"), _("restart"))) + line += 2 + + if self.package < 0: + #print package info + pack = self.getPackages() + i = 0 + for value in islice(pack, self.pos, self.pos + 5): + try: + println(line, mag(str(value.pid)) + ": " + value.name) + line += 1 + i += 1 + except Exception, e: + pass + for x in range(5 - i): + println(line, "") + line += 1 + else: + #print links info + pack = self.getLinks() + i = 0 + for value in islice(pack.links, self.pos, self.pos + 5): + try: + println(line, mag(value.fid) + ": %s | %s | %s" % ( + value.name, value.statusmsg, value.plugin)) + line += 1 + i += 1 + except Exception, e: + pass + for x in range(5 - i): + println(line, "") + line += 1 + + println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next")) + println(line + 1, mag("0.") + _(" back to main menu")) + + return line + 2 + + + def getPackages(self): + if self.cache and self.time + 2 < time(): + return self.cache + + if self.target == 1: + data = self.client.getQueue() + else: + data = self.client.getCollector() + + + self.cache = data + self.time = time() + + return data + + def getLinks(self): + if self.links and self.time + 1 < time(): + return self.links + + try: + data = self.client.getPackageData(self.package) + except: + data = PackageData(links=[]) + + self.links = data + self.time = time() + + return data + + def parseInput(self, inp, package=True): + inp = inp.strip() + if "-" in inp: + l, n, h = inp.partition("-") + l = int(l) + h = int(h) + r = range(l, h + 1) + + ret = [] + if package: + for p in self.cache: + if p.pid in r: + ret.append(p.pid) + else: + for l in self.links.links: + if l.lid in r: + ret.append(l.lid) + + return ret + + else: + return [int(x) for x in inp.split(",")] \ No newline at end of file diff --git a/module/cli/__init__.py b/module/cli/__init__.py new file mode 100644 index 000000000..fa8a09291 --- /dev/null +++ b/module/cli/__init__.py @@ -0,0 +1,2 @@ +from AddPackage import AddPackage +from ManageFiles import ManageFiles \ No newline at end of file diff --git a/module/cli/printer.py b/module/cli/printer.py new file mode 100644 index 000000000..c62c1800e --- /dev/null +++ b/module/cli/printer.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +def blue(string): + return "\033[1;34m" + unicode(string) + "\033[0m" + +def green(string): + return "\033[1;32m" + unicode(string) + "\033[0m" + +def yellow(string): + return "\033[1;33m" + unicode(string) + "\033[0m" + +def red(string): + return "\033[1;31m" + unicode(string) + "\033[0m" + +def cyan(string): + return "\033[1;36m" + unicode(string) + "\033[0m" + +def mag(string): + return "\033[1;35m" + unicode(string) + "\033[0m" + +def white(string): + return "\033[1;37m" + unicode(string) + "\033[0m" + +def println(line, content): + print "\033[" + str(line) + ";0H\033[2K" + content \ No newline at end of file diff --git a/module/lib/Getch.py b/module/lib/Getch.py new file mode 100644 index 000000000..22b7ea7f8 --- /dev/null +++ b/module/lib/Getch.py @@ -0,0 +1,76 @@ +class Getch: + """ + Gets a single character from standard input. Does not echo to + the screen. + """ + + def __init__(self): + try: + self.impl = _GetchWindows() + except ImportError: + try: + self.impl = _GetchMacCarbon() + except(AttributeError, ImportError): + self.impl = _GetchUnix() + + def __call__(self): return self.impl() + + +class _GetchUnix: + def __init__(self): + import tty + import sys + + def __call__(self): + import sys + import tty + import termios + + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + return ch + + +class _GetchWindows: + def __init__(self): + import msvcrt + + def __call__(self): + import msvcrt + + return msvcrt.getch() + +class _GetchMacCarbon: + """ + A function which returns the current ASCII key that is down; + if no ASCII key is down, the null string is returned. The + page http://www.mactech.com/macintosh-c/chap02-1.html was + very helpful in figuring out how to do this. + """ + + def __init__(self): + import Carbon + Carbon.Evt #see if it has this (in Unix, it doesn't) + + def __call__(self): + import Carbon + + if Carbon.Evt.EventAvail(0x0008)[0] == 0: # 0x0008 is the keyDownMask + return '' + else: + # + # The event contains the following info: + # (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1] + # + # The message (msg) contains the ASCII char which is + # extracted with the 0x000000FF charCodeMask; this + # number is converted to an ASCII character with chr() and + # returned + # + (what, msg, when, where, mod) = Carbon.Evt.GetNextEvent(0x0008)[1] + return chr(msg) \ No newline at end of file diff --git a/module/lib/SafeEval.py b/module/lib/SafeEval.py new file mode 100644 index 000000000..8ec9766e6 --- /dev/null +++ b/module/lib/SafeEval.py @@ -0,0 +1,70 @@ +## {{{ http://code.activestate.com/recipes/364469/ (r2) +import compiler + +class Unsafe_Source_Error(Exception): + def __init__(self,error,descr = None,node = None): + self.error = error + self.descr = descr + self.node = node + self.lineno = getattr(node,"lineno",None) + + def __repr__(self): + return "Line %d. %s: %s" % (self.lineno, self.error, self.descr) + __str__ = __repr__ + +class SafeEval(object): + + def visit(self, node,**kw): + cls = node.__class__ + meth = getattr(self,'visit'+cls.__name__,self.default) + return meth(node, **kw) + + def default(self, node, **kw): + for child in node.getChildNodes(): + return self.visit(child, **kw) + + visitExpression = default + + def visitConst(self, node, **kw): + return node.value + + def visitDict(self,node,**kw): + return dict([(self.visit(k),self.visit(v)) for k,v in node.items]) + + def visitTuple(self,node, **kw): + return tuple(self.visit(i) for i in node.nodes) + + def visitList(self,node, **kw): + return [self.visit(i) for i in node.nodes] + +class SafeEvalWithErrors(SafeEval): + + def default(self, node, **kw): + raise Unsafe_Source_Error("Unsupported source construct", + node.__class__,node) + + def visitName(self,node, **kw): + if node.name == "None": + return None + elif node.name == "True": + return True + elif node.name == "False": + return False + else: + raise Unsafe_Source_Error("Strings must be quoted", + node.name, node) + + # Add more specific errors if desired + + +def safe_eval(source, fail_on_error = True): + walker = fail_on_error and SafeEvalWithErrors() or SafeEval() + try: + ast = compiler.parse(source,"eval") + except SyntaxError, err: + raise + try: + return walker.visit(ast) + except Unsafe_Source_Error, err: + raise +## end of http://code.activestate.com/recipes/364469/ }}} diff --git a/module/lib/Unzip.py b/module/lib/Unzip.py new file mode 100644 index 000000000..f56fbe751 --- /dev/null +++ b/module/lib/Unzip.py @@ -0,0 +1,50 @@ +import zipfile +import os + +class Unzip: + def __init__(self): + pass + + def extract(self, file, dir): + if not dir.endswith(':') and not os.path.exists(dir): + os.mkdir(dir) + + zf = zipfile.ZipFile(file) + + # create directory structure to house files + self._createstructure(file, dir) + + # extract files to directory structure + for i, name in enumerate(zf.namelist()): + + if not name.endswith('/') and not name.endswith("config"): + print "extracting", name.replace("pyload/","") + outfile = open(os.path.join(dir, name.replace("pyload/","")), 'wb') + outfile.write(zf.read(name)) + outfile.flush() + outfile.close() + + def _createstructure(self, file, dir): + self._makedirs(self._listdirs(file), dir) + + def _makedirs(self, directories, basedir): + """ Create any directories that don't currently exist """ + for dir in directories: + curdir = os.path.join(basedir, dir) + if not os.path.exists(curdir): + os.mkdir(curdir) + + def _listdirs(self, file): + """ Grabs all the directories in the zip structure + This is necessary to create the structure before trying + to extract the file to it. """ + zf = zipfile.ZipFile(file) + + dirs = [] + + for name in zf.namelist(): + if name.endswith('/'): + dirs.append(name.replace("pyload/","")) + + dirs.sort() + return dirs diff --git a/module/plugins/PluginManager.py b/module/plugins/PluginManager.py index acfabde21..0ca060152 100644 --- a/module/plugins/PluginManager.py +++ b/module/plugins/PluginManager.py @@ -29,7 +29,7 @@ from traceback import print_exc try: from ast import literal_eval except ImportError: # python 2.5 - from module.SafeEval import safe_eval as literal_eval + from module.lib.SafeEval import safe_eval as literal_eval from module.ConfigParser import IGNORE diff --git a/module/remote/thriftbackend/Handler.py b/module/remote/thriftbackend/Handler.py index 9d38109db..9a7c1489e 100644 --- a/module/remote/thriftbackend/Handler.py +++ b/module/remote/thriftbackend/Handler.py @@ -157,6 +157,7 @@ class Handler(Iface): status.format_wait = pyfile.formatWait() status.wait_until = pyfile.waitUntil status.package = pyfile.package().name + status.packageID = pyfile.package().id data.append(status) return data diff --git a/module/remote/thriftbackend/pyload.thrift b/module/remote/thriftbackend/pyload.thrift index 8e399062d..c26334051 100644 --- a/module/remote/thriftbackend/pyload.thrift +++ b/module/remote/thriftbackend/pyload.thrift @@ -162,7 +162,7 @@ struct ServiceCall { 1: string plugin, 2: string func, 3: optional list arguments, - 4: optional bool parseArguments, //default True + 4: optional bool parseArguments, //default False } exception PackageDoesNotExists{ -- cgit v1.2.3