Subversion Repositories Scribus

Rev

Rev 24826 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
14647 cbradney 1
"""
2
This runtime module contains everything about running
3
Python and QtScript scripts inside Scribus.
4
 
5
Look at run_filename for details.
6
"""
7
import os
8
import hashlib
9
from ConfigParser import ConfigParser
10
 
11
import sip
12
from PyQt4.QtCore import QThread, QObject, QVariant
13
from PyQt4.QtGui import qApp,  QMessageBox
14
from PyQt4.QtScript import QScriptEngine, QScriptValue
15
 
16
from safe_eval import checkCode
17
import permitdlg
18
 
19
import __main__
20
 
21
from inspect import getargspec
22
 
23
class RuntimeConfig(ConfigParser):
24
 
25
    # I cannot use Scripter.preferences because a safe script could
26
    # mark other scripts as safe (=allowed) although they use import and
27
    # other (possible) dangerous stuff..
28
    # Perhaps I will find a better solution later.
29
 
30
    def __init__(self):
31
        ConfigParser.__init__(self)
32
        # XXX better use ScPaths->...
33
        path = os.path.expanduser("~/.scribus/scripter")
34
        if not os.path.exists(path):
35
            os.makedirs(path)
36
        self.filename = os.path.join(path, "runtime.cfg")
37
        self.read([self.filename])
38
 
39
 
40
    def save(self):
41
        fp = open(self.filename, "w")
42
        self.write(fp)
43
        fp.close()
44
 
45
 
46
    def set(self, section, key, value):
47
        if not self.has_section(section):
48
            self.add_section(section)
49
        ConfigParser.set(self, section, key, value)
50
        self.save()
51
 
52
 
53
    def getbool(self, section, key):
54
        value = self.get(section, key).strip().lower()
55
        if value and value in ["true", "on", "yes", "1"]:
56
            return True
57
        elif value and value in ["false", "off", "no", "0"]:
58
            return False
59
        else:
60
            raise ValueError, "Invalid boolean value %r" % value
61
 
62
 
63
runtime_config = RuntimeConfig()
64
 
65
extension_namespace = __main__.__dict__
66
 
67
 
68
qts_engine = None
69
 
70
# XXX share namespaces of Python and QtScript
71
 
72
class QtSRuntimeError(Exception):
73
    pass
74
 
75
 
76
def qts_func_decorator(func):
77
    def wrapper(context, engine):
78
        args = []
79
        (fargs, fvarargs, fvarkw, fdefaults) = getargspec(func)
80
        if len(fargs) and fargs[0] == "self":
81
            args.append(context.thisObject())
82
        for i in xrange(context.argumentCount()):
83
            args.append(context.argument(i))
84
        try:
85
            result = func(*args)
86
        except Exception, e:
87
            # XXX correct behaviour?
88
            # http://lists.trolltech.com/qt-interest/2007-06/thread00892-0.html
89
            return context.throwValue(QScriptValue(engine, str(e)))
90
        if result:
91
            return QScriptValue(engine, result)
92
        else:
93
            return QScriptValue()
94
    return wrapper
95
 
96
 
97
@qts_func_decorator
98
def alert(msg_qsv):
99
    msg = msg_qsv.toString()
100
    QMessageBox.information(Scripter.dialogs.mainWindow.qt, "Alert", msg)
101
 
102
 
103
def update_qs_namespace(engine, ns):
104
    go = engine.globalObject()
105
    for name, value in ns.items():
106
        if isinstance(value, QObject):
107
            value = engine.newQObject(value)
108
        elif callable(value):
109
            value = engine.newFunction(value)
110
        #elif not isinstance(value, QScriptValue):
111
        #    value = QScriptValue(engine, value)
112
        go.setProperty(name, value)
113
 
114
 
115
def newQScriptEngine():
116
    engine = QScriptEngine()
117
    update_qs_namespace(engine,
118
       {
119
          "Application": qApp,
120
          "Scripter": Scripter.qt,
121
          "alert": alert
122
       })
123
    return engine
124
 
125
 
126
def run_qtscript(filename, subroutine=None, extension=False):
127
    global qts_engine
128
    if not extension:
129
        engine = newQScriptEngine()
130
    else:
131
        engine = qts_engine = qts_engine or newQScriptEngine()
132
    code = open(filename).read()
133
    engine.clearExceptions()
134
    result = engine.evaluate(code)
135
    engine.collectGarbage()
136
    if not engine.hasUncaughtException() and subroutine:
137
        sub = engine.globalObject().property(subroutine)
138
        sub.call()
139
    if engine.hasUncaughtException():
140
        bt = engine.uncaughtExceptionBacktrace()
141
        raise QtSRuntimeError("%s\nTraceback:\%s" % (
142
              str(engine.uncaughtException().toString()),
143
              "\n".join(["  %s" % l for l in list(bt)])))
144
 
145
 
146
def hash_source(filename, source=None):
147
    # I gueses sha256 is safe enough without collisions?
148
    source = source or open(filename).read()
149
    return "%s:%s:%s" % (
150
        os.path.basename(filename), len(filename), hashlib.sha256(source).hexdigest())
151
 
152
 
153
def check_python(filename):
154
    filename = os.path.abspath(os.path.expanduser(filename))
155
    path = os.path.dirname(filename)
156
    # Allow files from global autoload folder by default. 
157
    # XXX Good idea?
158
    if path == os.path.join(Scripter.path, "autoload"):
159
        return True
160
    code = open(filename).read()
161
    h = hash_source(filename, code)
162
    if runtime_config.has_option("permissions", h):
163
        return runtime_config.getbool("permissions", h)
25090 jghali 164
 
165
    problems = checkCode(code)
166
    if problems and len(problems) == 1 and isinstance(problems[0], SyntaxError):
167
        return True # let's ignore it and let excepthook handle the error later
168
    elif problems:
169
        ok = permitdlg.ask(filename, problems)
170
        if ok == -2: # deny and remember
14647 cbradney 171
            runtime_config.set("permissions", h, False)
172
            return False
173
        elif ok == 2: # deny
174
            return False
175
        elif ok == -1: # allow and remember
176
            runtime_config.set("permissions", h, True)
177
        elif ok == 1: # allow but now remember
178
            pass
179
        else:
180
            raise ValueError, "Inknown return code for permission dialog: %r" % ok
181
    return True
182
 
183
 
184
def run_python(filename, subroutine=None, extension=False):
185
    if not extension:
186
        namespace = {
18077 jghali 187
        "__name__": "__scribus__",
188
        "__file__": filename
14647 cbradney 189
        }
190
    else:
191
        namespace = extension_namespace
192
    if not check_python(filename):
193
        return
194
    execfile(filename, namespace)
195
    if subroutine:
196
        sub = namespace[subroutine]
197
        sub()
198
    if not extension:
199
        del namespace
200
 
201
 
202
threads = []
203
 
204
class RunThread(QThread):
205
 
206
 
207
    def __init__(self, func, *args):
208
        QThread.__init__(self, Scripter.qt)
209
        self.func = func
210
        self.args = args
211
 
212
 
213
    def run(self):
214
        threads.append(self)
215
        self.func(*self.args)
216
        threads.remove(self)
217
 
218
 
219
def run_background(func, *args):
220
    thread = RunThread(func, *args)
221
    thread.start()
222
    # XXX: connect done signal with cleanup?
223
    return thread
224
 
225
 
226
 
227
def mark_keep():
228
    """
229
    mark every child of Scripter.collector to keep
230
    """
231
    for child in Scripter.collector.children():
232
        if hasattr(child, "qt"): child = child.qt        
233
        child.setProperty("keep", QVariant(True))
234
 
235
 
236
 
237
def cleanup():
238
    """
239
    delete every child which is not marked as keep
240
    """
241
    for child in Scripter.collector.children():
242
        if hasattr(child, "qt"): child = child.qt
243
        v = child.property("keep")
244
        if v and v.toBool() == True:
245
            #print "Keeping", child
246
            continue
247
        print "* deleting collected", child
248
        sip.delete(child)
249
 
250
 
251
 
252
def run_filename(filename, subroutine=None, extension=False, background=False):
253
    """
254
    Call this function to run a script and nothing else.
255
    It will do everything for you, including garbage collection
256
    for QtScript (very simple implementation, see mark_keep and cleanup).
257
    Running as extension uses the __main__ namespace and does not
258
    delete objects after execution.
259
    Running in background as a thread is not much tested and
260
    should only be used for non-GUI scripts.
261
    """
262
    mark_keep()
263
    if background:
264
        run_func = run_background
265
    else:
266
        run_func = lambda func, *args: func(*args)
267
    if filename.endswith((".sqts", ".qts", ".sjs", ".js")):
268
        run_func(run_qtscript, filename, subroutine, extension)
24665 jghali 269
    else:
270
        run_func(run_python, filename, subroutine, extension)
271
    if not background and not extension:
272
        # XXX: make sure this is called if an exception occurs...
273
        cleanup()