Merge lp:~shakaran/userwebkit/class-refactor into lp:userwebkit
- class-refactor
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~shakaran/userwebkit/class-refactor |
Merge into: | lp:userwebkit |
Diff against target: |
705 lines (+334/-204) 8 files modified
couchview.py (+158/-0) debian/copyright (+1/-1) demo-app.py (+7/-7) demo-app2.py (+6/-6) hub.py (+81/-0) inspector.py (+54/-0) test_userwebkit.py (+12/-10) userwebkit.py (+15/-180) |
To merge this branch: | bzr merge lp:~shakaran/userwebkit/class-refactor |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jason Gerard DeRose | Disapprove | ||
Review via email: mp+123169@code.launchpad.net |
Commit message
Description of the change
Major changes:
- Split userwebkit classes (Hub, Inspector and CouchView) in different files for reuse components easily. Useful when you want use only a component from other project and don't import all module or only a part. Also it makes more easily to track changes with more reduced files.
- Update test according to last refactor.
Minor changes:
- Update copyright year (debian file), demo-app.py and demo-app2.py
- Remove blank lines and update shebang in demo-app.py and demo-app2.py
Problems to review:
- I cannot fix one test regarding to FactoryHub signals. I think that signals are not properly erased in teardown test. - Also check if setup.py include all refactored files in installation.
Unmerged revisions
- 70. By Angel Guzman Maeso <email address hidden>
-
Update test according to last refactor. Only I cannot fix one test. I think that signals are not properly erased in teardown test. Also check if setup.py include all refactored files in installation. Needs review
- 69. By Angel Guzman Maeso <email address hidden>
-
Split userwebkit classes (Hub, Inspector and CouchView) in different files for reuse components easily
- 68. By Angel Guzman Maeso <email address hidden>
-
Update copyright year, remove blank lines and update shebang
- 67. By Angel Guzman Maeso <email address hidden>
-
Update copyright year
Preview Diff
1 | === added file 'couchview.py' |
2 | --- couchview.py 1970-01-01 00:00:00 +0000 |
3 | +++ couchview.py 2012-09-06 20:58:20 +0000 |
4 | @@ -0,0 +1,158 @@ |
5 | +#!/usr/bin/env python |
6 | +# -*- coding: utf-8; tab-width: 4; mode: python -*- |
7 | +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- |
8 | +# vi: set ft=python sts=4 ts=4 sw=4 noet |
9 | +# |
10 | +# novacut: the collaborative video editor |
11 | +# Copyright (C) 2011-2012 Novacut Inc |
12 | +# |
13 | +# This file is part of `novacut`. |
14 | +# |
15 | +# `novacut` is free software: you can redistribute it and/or modify it under |
16 | +# the terms of the GNU Affero General Public License as published by the Free |
17 | +# Software Foundation, either version 3 of the License, or (at your option) |
18 | +# any later version. |
19 | +# |
20 | +# `novacut` is distributed in the hope that it will be useful, but WITHOUT ANY |
21 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
22 | +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for |
23 | +# more details. |
24 | +# |
25 | +# You should have received a copy of the GNU Affero General Public License |
26 | +# along with `novacut`. If not, see <http://www.gnu.org/licenses/>. |
27 | +# |
28 | +# Authors: |
29 | +# Jason Gerard DeRose <jderose@novacut.com> |
30 | + |
31 | +from urllib.parse import urlparse, parse_qsl |
32 | +from gi.repository import GObject, Gtk, WebKit |
33 | +from gi.repository.GObject import TYPE_PYOBJECT |
34 | +from microfiber import _oauth_header, _basic_auth_header |
35 | + |
36 | +import logging |
37 | +log = logging.getLogger('userwebkit') |
38 | + |
39 | +class CouchView(WebKit.WebView): |
40 | + __gsignals__ = { |
41 | + 'open': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, |
42 | + [TYPE_PYOBJECT] |
43 | + ), |
44 | + } |
45 | + |
46 | + def __init__(self, env=None, dmedia_resolver=None): |
47 | + super().__init__() |
48 | + self._logging_enabled = False |
49 | + ## |
50 | + self.connect('resource-request-starting', self._on_request) |
51 | + self.connect('navigation-policy-decision-requested', |
52 | + self._on_nav_policy_decision |
53 | + ) |
54 | + self.set_env(env) |
55 | + self._dmedia_resolver = dmedia_resolver |
56 | + |
57 | + def set_env(self, env): |
58 | + self._env = env |
59 | + if env is None: |
60 | + self._u = None |
61 | + self._oauth = None |
62 | + self._basic = None |
63 | + return |
64 | + self._u = urlparse(env['url']) |
65 | + self._oauth = env.get('oauth') |
66 | + self._basic = env.get('basic') |
67 | + |
68 | + def set_recv(self, recv): |
69 | + """ Executed by Hub object when recieve data """ |
70 | + self._recv = recv |
71 | + self.connect('notify::title', self._on_notify_title) |
72 | + |
73 | + def enable_logging(self): |
74 | + """ |
75 | + Enables send console message to Python logging |
76 | + """ |
77 | + if not self._logging_enabled: |
78 | + self._logging_enabled = True |
79 | + self.connect('console-message', self._on_console_message) |
80 | + |
81 | + def _on_console_message(self, view, message, line, source_id): |
82 | + log.debug('%s @%s: %s', source_id, line, message) |
83 | + |
84 | + def enable_view_editable(self): |
85 | + """ |
86 | + Enables the webkit view as editable |
87 | + """ |
88 | + self.set_editable(True) |
89 | + |
90 | + def _on_request(self, view, frame, resource, request, response): |
91 | + if self._env is None: |
92 | + return |
93 | + uri = request.get_uri() |
94 | + message = request.get_message() |
95 | + if uri.startswith('dmedia'): |
96 | + if self._dmedia_resolver is None: |
97 | + request.set_uri('') |
98 | + else: |
99 | + request.set_uri(self._dmedia_resolver(uri)) |
100 | + return |
101 | + u = urlparse(uri) |
102 | + if u.netloc != self._u.netloc: |
103 | + return |
104 | + if u.scheme != self._u.scheme: |
105 | + return |
106 | + if self._oauth: |
107 | + query = dict(parse_qsl(u.query)) |
108 | + if u.query and not query: |
109 | + query = {u.query: ''} |
110 | + baseurl = ''.join([u.scheme, '://', u.netloc, u.path]) |
111 | + print ('baseurl:' + str(baseurl)) |
112 | + method = message.method |
113 | + print ('method:' + str(method)) |
114 | + h = _oauth_header(self._oauth, method, baseurl, query) |
115 | + elif self._basic: |
116 | + h = _basic_auth_header(self._basic) |
117 | + else: |
118 | + return |
119 | + |
120 | + # Clean headers previously to add new headers (reset) |
121 | + # Removes all the headers listed in the Connection header. |
122 | + message.request_headers.clean_connection_headers() |
123 | + |
124 | + for (key, value) in h.items(): |
125 | + message.request_headers.append(key, value) |
126 | + |
127 | + def _on_nav_policy_decision(self, view, frame, request, nav, policy): |
128 | + """ |
129 | + Handle user trying to Navigate away from current page. |
130 | + |
131 | + Note that this will be called before `CouchView._on_resource_request()`. |
132 | + |
133 | + The *policy* arg is a ``WebPolicyDecision`` instance. To handle the |
134 | + decision, call one of: |
135 | + |
136 | + * ``WebPolicyDecision.ignore()`` |
137 | + * ``WebPolicyDecision.use()`` |
138 | + * ``WebPolicyDecision.download()`` |
139 | + |
140 | + And then return ``True``. |
141 | + |
142 | + Otherwise, return ``False`` or ``None`` to have the WebKit default |
143 | + behavior apply. |
144 | + """ |
145 | + if self._env is None: |
146 | + return |
147 | + uri = request.get_uri() |
148 | + u = urlparse(uri) |
149 | + if u.netloc == self._u.netloc or u.scheme == 'file': |
150 | + return False |
151 | + if u.scheme in ('http', 'https'): |
152 | + self.emit('open', uri) |
153 | + policy.ignore() |
154 | + return True |
155 | + |
156 | + def _on_notify_title(self, view, notify): |
157 | + title = view.get_property('title') |
158 | + log.debug('Title %s', str(title)) |
159 | + if title is None: |
160 | + return |
161 | + self._recv(title) |
162 | + |
163 | \ No newline at end of file |
164 | |
165 | === modified file 'debian/copyright' |
166 | --- debian/copyright 2011-09-26 10:41:04 +0000 |
167 | +++ debian/copyright 2012-09-06 20:58:20 +0000 |
168 | @@ -6,7 +6,7 @@ |
169 | |
170 | Copyright and license: |
171 | |
172 | -| Copyright (C) 2011 Novacut Inc |
173 | +| Copyright (C) 2011-2012 Novacut Inc |
174 | | |
175 | | This file is part of `userwebkit`. |
176 | | |
177 | |
178 | === modified file 'demo-app.py' |
179 | --- demo-app.py 2012-04-14 08:20:09 +0000 |
180 | +++ demo-app.py 2012-09-06 20:58:20 +0000 |
181 | @@ -1,7 +1,10 @@ |
182 | -#!/usr/bin/python3 |
183 | - |
184 | +#!/usr/bin/env python3 |
185 | +# -*- coding: utf-8; tab-width: 4; mode: python -*- |
186 | +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- |
187 | +# vi: set ft=python sts=4 ts=4 sw=4 noet |
188 | +# |
189 | # novacut: the collaborative video editor |
190 | -# Copyright (C) 2011 Novacut Inc |
191 | +# Copyright (C) 2011-2012 Novacut Inc |
192 | # |
193 | # This file is part of `novacut`. |
194 | # |
195 | @@ -27,7 +30,6 @@ |
196 | import userwebkit |
197 | from userwebkit import BaseApp |
198 | |
199 | - |
200 | class App(BaseApp): |
201 | name = 'demo' |
202 | dbname = 'demo-0' |
203 | @@ -86,7 +88,5 @@ |
204 | def dmedia_resolver(self, uri): |
205 | return self.proxy.ResolveURI(uri) |
206 | |
207 | - |
208 | app = App() |
209 | -app.run() |
210 | - |
211 | +app.run() |
212 | \ No newline at end of file |
213 | |
214 | === modified file 'demo-app2.py' |
215 | --- demo-app2.py 2012-04-14 08:10:53 +0000 |
216 | +++ demo-app2.py 2012-09-06 20:58:20 +0000 |
217 | @@ -1,5 +1,8 @@ |
218 | -#!/usr/bin/python3 |
219 | - |
220 | +#!/usr/bin/env python3 |
221 | +# -*- coding: utf-8; tab-width: 4; mode: python -*- |
222 | +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- |
223 | +# vi: set ft=python sts=4 ts=4 sw=4 noet |
224 | +# |
225 | # novacut: the collaborative video editor |
226 | # Copyright (C) 2011 Novacut Inc |
227 | # |
228 | @@ -24,7 +27,6 @@ |
229 | import userwebkit |
230 | from userwebkit import BaseApp |
231 | |
232 | - |
233 | class App(BaseApp): |
234 | name = 'demo' |
235 | dbname = 'demo-0' |
236 | @@ -34,7 +36,5 @@ |
237 | page = 'index2.html' |
238 | proxy_bus = 'org.freedesktop.Dmedia' |
239 | |
240 | - |
241 | app = App() |
242 | -app.run() |
243 | - |
244 | +app.run() |
245 | \ No newline at end of file |
246 | |
247 | === added file 'hub.py' |
248 | --- hub.py 1970-01-01 00:00:00 +0000 |
249 | +++ hub.py 2012-09-06 20:58:20 +0000 |
250 | @@ -0,0 +1,81 @@ |
251 | +# -*- coding: utf-8; tab-width: 4; mode: python -*- |
252 | +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- |
253 | +# vi: set ft=python sts=4 ts=4 sw=4 noet |
254 | +# |
255 | +# userwebkit: so WebKitGtk apps can to talk to a usercouch |
256 | +# Copyright (C) 2011-2012 Novacut Inc |
257 | +# |
258 | +# This file is part of `userwebkit`. |
259 | +# |
260 | +# `userwebkit` is free software: you can redistribute it and/or modify it under |
261 | +# the terms of the GNU Lesser General Public License as published by the Free |
262 | +# Software Foundation, either version 3 of the License, or (at your option) any |
263 | +# later version. |
264 | +# |
265 | +# `userwebkit` is distributed in the hope that it will be useful, but WITHOUT |
266 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
267 | +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
268 | +# details. |
269 | +# |
270 | +# You should have received a copy of the GNU Lesser General Public License along |
271 | +# with `userwebkit`. If not, see <http://www.gnu.org/licenses/>. |
272 | +# |
273 | +# Authors: |
274 | +# Jason Gerard DeRose <jderose@novacut.com> |
275 | +from gi.repository import GObject |
276 | + |
277 | +import json |
278 | + |
279 | +import logging |
280 | +log = logging.getLogger('userwebkit') |
281 | + |
282 | +def iter_gsignals(signals): |
283 | + print (signals) |
284 | + assert isinstance(signals, dict) |
285 | + for (name, argnames) in signals.items(): |
286 | + assert isinstance(argnames, list) |
287 | + args = [GObject.TYPE_PYOBJECT for argname in argnames] |
288 | + yield (name, (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, args)) |
289 | + |
290 | +def hub_factory(signals): |
291 | + if signals: |
292 | + class FactoryHub(Hub): |
293 | + __gsignals__ = dict(iter_gsignals(signals)) |
294 | + return FactoryHub |
295 | + return Hub |
296 | + |
297 | +class Hub(GObject.GObject): |
298 | + def __init__(self, view): |
299 | + super().__init__() |
300 | + self._view = view |
301 | + |
302 | + # Key aspect, send via notify::title to view the recieved data |
303 | + view.set_recv(self.recv) |
304 | + |
305 | + def recv(self, data): |
306 | + try: |
307 | + obj = json.loads(data) |
308 | + log.debug('Hub event(recv): %s', str(obj)) |
309 | + |
310 | + if obj['args'] == None: |
311 | + self.emit(obj['signal'], None) |
312 | + else: |
313 | + self.emit(obj['signal'], *obj['args']) |
314 | + except ValueError: |
315 | + pass |
316 | + |
317 | + def send(self, signal, *args): |
318 | + """ |
319 | + Emit a signal by calling the JavaScript Signal.recv() function. |
320 | + """ |
321 | + |
322 | + log.debug('Hub event(send): signal=%s', str(signal)) |
323 | + |
324 | + script = 'Hub.recv({!r})'.format( |
325 | + json.dumps({'signal': signal, 'args': args}) |
326 | + ) |
327 | + |
328 | + log.debug('Executing javascript:\n%s', str(script)) |
329 | + |
330 | + self._view.execute_script(script) |
331 | + self.emit(signal, *args) |
332 | \ No newline at end of file |
333 | |
334 | === added file 'inspector.py' |
335 | --- inspector.py 1970-01-01 00:00:00 +0000 |
336 | +++ inspector.py 2012-09-06 20:58:20 +0000 |
337 | @@ -0,0 +1,54 @@ |
338 | +# -*- coding: utf-8; tab-width: 4; mode: python -*- |
339 | +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- |
340 | +# vi: set ft=python sts=4 ts=4 sw=4 noet |
341 | +# |
342 | +# userwebkit: so WebKitGtk apps can to talk to a usercouch |
343 | +# Copyright (C) 2011-2012 Novacut Inc |
344 | +# |
345 | +# This file is part of `userwebkit`. |
346 | +# |
347 | +# `userwebkit` is free software: you can redistribute it and/or modify it under |
348 | +# the terms of the GNU Lesser General Public License as published by the Free |
349 | +# Software Foundation, either version 3 of the License, or (at your option) any |
350 | +# later version. |
351 | +# |
352 | +# `userwebkit` is distributed in the hope that it will be useful, but WITHOUT |
353 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
354 | +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
355 | +# details. |
356 | +# |
357 | +# You should have received a copy of the GNU Lesser General Public License along |
358 | +# with `userwebkit`. If not, see <http://www.gnu.org/licenses/>. |
359 | +# |
360 | +# Authors: |
361 | +# Jason Gerard DeRose <jderose@novacut.com> |
362 | + |
363 | +from gi.repository import Gtk |
364 | +from couchview import CouchView |
365 | + |
366 | +class Inspector(Gtk.VBox): |
367 | + def __init__(self, env): |
368 | + super().__init__() |
369 | + |
370 | + hbox = Gtk.HBox() |
371 | + self.pack_start(hbox, False, False, 0) |
372 | + |
373 | + close = Gtk.Button(stock=Gtk.STOCK_CLOSE) |
374 | + hbox.pack_start(close, False, False, 2) |
375 | + close.connect('clicked', self.on_close) |
376 | + |
377 | + self.reload = Gtk.Button('Reload') |
378 | + hbox.pack_start(self.reload, False, False, 2) |
379 | + |
380 | + self.futon = Gtk.Button('CouchDB Futon') |
381 | + hbox.pack_start(self.futon, False, False, 2) |
382 | + |
383 | + scroll = Gtk.ScrolledWindow() |
384 | + self.pack_start(scroll, True, True, 0) |
385 | + scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) |
386 | + |
387 | + self.view = CouchView(env) |
388 | + scroll.add(self.view) |
389 | + |
390 | + def on_close(self, button): |
391 | + self.destroy() |
392 | \ No newline at end of file |
393 | |
394 | === modified file 'test_userwebkit.py' |
395 | --- test_userwebkit.py 2012-08-13 06:56:11 +0000 |
396 | +++ test_userwebkit.py 2012-09-06 20:58:20 +0000 |
397 | @@ -37,7 +37,8 @@ |
398 | from gi.repository.GObject import SIGNAL_RUN_LAST, TYPE_NONE, TYPE_PYOBJECT |
399 | |
400 | import userwebkit |
401 | - |
402 | +import hub |
403 | +import couchview |
404 | |
405 | orig_log = userwebkit.log |
406 | random = SystemRandom() |
407 | @@ -144,7 +145,7 @@ |
408 | class TestFunctions(TestCase): |
409 | def test_iter_gsignals(self): |
410 | self.assertEqual( |
411 | - dict(userwebkit.iter_gsignals({})), |
412 | + dict(hub.iter_gsignals({})), # Breaks here, signals are filled by test_hub_factory and it get no reset? |
413 | {} |
414 | ) |
415 | signals = { |
416 | @@ -158,21 +159,22 @@ |
417 | 'baz': (SIGNAL_RUN_LAST, TYPE_NONE, [TYPE_PYOBJECT, TYPE_PYOBJECT]), |
418 | } |
419 | self.assertEqual( |
420 | - dict(userwebkit.iter_gsignals(signals)), |
421 | + dict(hub.iter_gsignals(signals)), |
422 | gsignals |
423 | ) |
424 | |
425 | def test_hub_factory(self): |
426 | - self.assertIs(userwebkit.hub_factory(None), userwebkit.Hub) |
427 | - self.assertIs(userwebkit.hub_factory({}), userwebkit.Hub) |
428 | + global hub # Avoid UnboundLocalError |
429 | + self.assertIs(hub.hub_factory(None), hub.Hub) |
430 | + self.assertIs(hub.hub_factory({}), hub.Hub) |
431 | signals = { |
432 | 'foo': [], |
433 | 'bar': ['one'], |
434 | 'baz': ['one', 'two'], |
435 | } |
436 | - klass = userwebkit.hub_factory(signals) |
437 | - self.assertIsNot(klass, userwebkit.Hub) |
438 | - self.assertTrue(issubclass(klass, userwebkit.Hub)) |
439 | + klass = hub.hub_factory(signals) |
440 | + self.assertIsNot(klass, hub.Hub) |
441 | + self.assertTrue(issubclass(klass, hub.Hub)) |
442 | self.assertEqual(klass.__name__, 'FactoryHub') |
443 | |
444 | # Make sure we can connect to all the expected signals: |
445 | @@ -263,9 +265,9 @@ |
446 | self.assertIsNone(view.enable_logging()) |
447 | |
448 | def test_on_console_message(self): |
449 | - view = userwebkit.CouchView() |
450 | + view = couchview.CouchView() |
451 | log = DummyLogger() |
452 | - userwebkit.log = log |
453 | + couchview.log = log |
454 | message = 'hello ' + random_id() |
455 | line = 25 |
456 | source_id = 'http://localhost:36163/_intree/index.html' |
457 | |
458 | === modified file 'userwebkit.py' |
459 | --- userwebkit.py 2012-08-29 00:04:45 +0000 |
460 | +++ userwebkit.py 2012-09-06 20:58:20 +0000 |
461 | @@ -1,5 +1,9 @@ |
462 | +# -*- coding: utf-8; tab-width: 4; mode: python -*- |
463 | +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- |
464 | +# vi: set ft=python sts=4 ts=4 sw=4 noet |
465 | +# |
466 | # userwebkit: so WebKitGtk apps can to talk to a usercouch |
467 | -# Copyright (C) 2011 Novacut Inc |
468 | +# Copyright (C) 2011-2012 Novacut Inc |
469 | # |
470 | # This file is part of `userwebkit`. |
471 | # |
472 | @@ -29,14 +33,11 @@ |
473 | import json |
474 | import optparse |
475 | import logging |
476 | +from gi.repository import GObject, Gtk |
477 | |
478 | import microfiber |
479 | -from microfiber import _oauth_header, _basic_auth_header |
480 | import dbus |
481 | from dbus.mainloop.glib import DBusGMainLoop |
482 | -from gi.repository import GObject, Gtk, WebKit |
483 | -from gi.repository.GObject import TYPE_PYOBJECT |
484 | - |
485 | |
486 | __version__ = '12.09.0' |
487 | APPS = '/usr/share/couchdb/apps/' |
488 | @@ -44,187 +45,15 @@ |
489 | DBusGMainLoop(set_as_default=True) |
490 | log = logging.getLogger('userwebkit') |
491 | |
492 | - |
493 | def handler(d): |
494 | assert path.abspath(d) == d |
495 | return '{{couch_httpd_misc_handlers, handle_utils_dir_req, {}}}'.format( |
496 | json.dumps(d) |
497 | ) |
498 | |
499 | - |
500 | -class CouchView(WebKit.WebView): |
501 | - __gsignals__ = { |
502 | - 'open': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, |
503 | - [TYPE_PYOBJECT] |
504 | - ), |
505 | - } |
506 | - |
507 | - def __init__(self, env=None, dmedia_resolver=None): |
508 | - super().__init__() |
509 | - self._logging_enabled = False |
510 | - self.connect('resource-request-starting', self._on_request) |
511 | - self.connect('navigation-policy-decision-requested', |
512 | - self._on_nav_policy_decision |
513 | - ) |
514 | - self.set_env(env) |
515 | - self._dmedia_resolver = dmedia_resolver |
516 | - |
517 | - def set_env(self, env): |
518 | - self._env = env |
519 | - if env is None: |
520 | - self._u = None |
521 | - self._oauth = None |
522 | - self._basic = None |
523 | - return |
524 | - self._u = urlparse(env['url']) |
525 | - self._oauth = env.get('oauth') |
526 | - self._basic = env.get('basic') |
527 | - |
528 | - def set_recv(self, recv): |
529 | - self._recv = recv |
530 | - self.connect('notify::title', self._on_notify_title) |
531 | - |
532 | - def enable_logging(self): |
533 | - if not self._logging_enabled: |
534 | - self._logging_enabled = True |
535 | - self.connect('console-message', self._on_console_message) |
536 | - |
537 | - def _on_console_message(self, view, message, line, source_id): |
538 | - log.debug('%s @%s: %s', source_id, line, message) |
539 | - |
540 | - def _on_request(self, view, frame, resource, request, response): |
541 | - if self._env is None: |
542 | - return |
543 | - uri = request.get_uri() |
544 | - message = request.get_message() |
545 | - if uri.startswith('dmedia'): |
546 | - if self._dmedia_resolver is None: |
547 | - request.set_uri('') |
548 | - else: |
549 | - request.set_uri(self._dmedia_resolver(uri)) |
550 | - return |
551 | - u = urlparse(uri) |
552 | - if u.netloc != self._u.netloc: |
553 | - return |
554 | - if u.scheme != self._u.scheme: |
555 | - return |
556 | - if self._oauth: |
557 | - query = dict(parse_qsl(u.query)) |
558 | - if u.query and not query: |
559 | - query = {u.query: ''} |
560 | - baseurl = ''.join([u.scheme, '://', u.netloc, u.path]) |
561 | - method = message.method |
562 | - h = _oauth_header(self._oauth, method, baseurl, query) |
563 | - elif self._basic: |
564 | - h = _basic_auth_header(self._basic) |
565 | - else: |
566 | - return |
567 | - for (key, value) in h.items(): |
568 | - message.request_headers.append(key, value) |
569 | - |
570 | - def _on_nav_policy_decision(self, view, frame, request, nav, policy): |
571 | - """ |
572 | - Handle user trying to Navigate away from current page. |
573 | - |
574 | - Note that this will be called before `CouchView._on_resource_request()`. |
575 | - |
576 | - The *policy* arg is a ``WebPolicyDecision`` instance. To handle the |
577 | - decision, call one of: |
578 | - |
579 | - * ``WebPolicyDecision.ignore()`` |
580 | - * ``WebPolicyDecision.use()`` |
581 | - * ``WebPolicyDecision.download()`` |
582 | - |
583 | - And then return ``True``. |
584 | - |
585 | - Otherwise, return ``False`` or ``None`` to have the WebKit default |
586 | - behavior apply. |
587 | - """ |
588 | - if self._env is None: |
589 | - return |
590 | - uri = request.get_uri() |
591 | - u = urlparse(uri) |
592 | - if u.netloc == self._u.netloc or u.scheme == 'file': |
593 | - return False |
594 | - if u.scheme in ('http', 'https'): |
595 | - self.emit('open', uri) |
596 | - policy.ignore() |
597 | - return True |
598 | - |
599 | - def _on_notify_title(self, view, notify): |
600 | - title = view.get_property('title') |
601 | - if title is None: |
602 | - return |
603 | - self._recv(title) |
604 | - |
605 | - |
606 | -class Inspector(Gtk.VBox): |
607 | - def __init__(self, env): |
608 | - super().__init__() |
609 | - |
610 | - hbox = Gtk.HBox() |
611 | - self.pack_start(hbox, False, False, 0) |
612 | - |
613 | - close = Gtk.Button(stock=Gtk.STOCK_CLOSE) |
614 | - hbox.pack_start(close, False, False, 2) |
615 | - close.connect('clicked', self.on_close) |
616 | - |
617 | - self.reload = Gtk.Button('Reload') |
618 | - hbox.pack_start(self.reload, False, False, 2) |
619 | - |
620 | - self.futon = Gtk.Button('CouchDB Futon') |
621 | - hbox.pack_start(self.futon, False, False, 2) |
622 | - |
623 | - scroll = Gtk.ScrolledWindow() |
624 | - self.pack_start(scroll, True, True, 0) |
625 | - scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) |
626 | - |
627 | - self.view = CouchView(env) |
628 | - scroll.add(self.view) |
629 | - |
630 | - def on_close(self, button): |
631 | - self.destroy() |
632 | - |
633 | - |
634 | -class Hub(GObject.GObject): |
635 | - def __init__(self, view): |
636 | - super().__init__() |
637 | - self._view = view |
638 | - view.set_recv(self.recv) |
639 | - |
640 | - def recv(self, data): |
641 | - try: |
642 | - obj = json.loads(data) |
643 | - self.emit(obj['signal'], *obj['args']) |
644 | - except ValueError: |
645 | - pass |
646 | - |
647 | - def send(self, signal, *args): |
648 | - """ |
649 | - Emit a signal by calling the JavaScript Signal.recv() function. |
650 | - """ |
651 | - script = 'Hub.recv({!r})'.format( |
652 | - json.dumps({'signal': signal, 'args': args}) |
653 | - ) |
654 | - self._view.execute_script(script) |
655 | - self.emit(signal, *args) |
656 | - |
657 | - |
658 | -def iter_gsignals(signals): |
659 | - assert isinstance(signals, dict) |
660 | - for (name, argnames) in signals.items(): |
661 | - assert isinstance(argnames, list) |
662 | - args = [TYPE_PYOBJECT for argname in argnames] |
663 | - yield (name, (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, args)) |
664 | - |
665 | - |
666 | -def hub_factory(signals): |
667 | - if signals: |
668 | - class FactoryHub(Hub): |
669 | - __gsignals__ = dict(iter_gsignals(signals)) |
670 | - return FactoryHub |
671 | - return Hub |
672 | - |
673 | +from couchview import CouchView |
674 | +from inspector import Inspector |
675 | +from hub import hub_factory |
676 | |
677 | class BaseApp(object): |
678 | name = 'userwebkit' # The namespace of your app, likely source package name |
679 | @@ -235,6 +64,7 @@ |
680 | |
681 | enable_inspector = True # If True, enable WebKit inspector |
682 | enable_logging = True # If True, send console message to Python logging |
683 | + view_editable = False # If True, the webkit view is editable |
684 | |
685 | dmedia_resolver = None # Callback to resolve Dmedia URIs |
686 | |
687 | @@ -352,6 +182,10 @@ |
688 | |
689 | # Add the CouchView |
690 | self.view = CouchView(None, self.dmedia_resolver) |
691 | + |
692 | + if self.view_editable: |
693 | + self.view.enable_view_editable() |
694 | + |
695 | self.view.connect('open', self.on_open) |
696 | self.scroll.add(self.view) |
697 | if self.enable_inspector: |
698 | @@ -360,6 +194,7 @@ |
699 | inspector.connect('inspect-web-view', self.on_inspect) |
700 | if self.enable_logging: |
701 | self.view.enable_logging() |
702 | + |
703 | self.view.show() |
704 | |
705 | # Create the hub |
Sorry Angel, I'm gonna say no on this one :)
As userwebkit.py is less than 500 lines, I don't think this split is yet needed for maintainability. Plus, I think we need more work on the existing API before we know how such a split should be done.
The startup time of a Python app also has a somewhat steep per-module overhead. So in terms of looking at performance and memory usage, it's generally more import to split modules based on what those modules themselves then import. And in this case, everything needs to import `gi.repository.Gtk` (which is where the biggest overhead is), so if you're using anything from userwebkit.py, there is little benefit to importing just a fragment of it.
One more note: once a split like this is done, the best way to do it is to bzr mv the current userwebkit.py into userwebkit/ __init_ _.py and put new modules inside the userwebkit/ Python package.
I'm going to do the above when I bring back the JavaScript unit tester that used to be in Dmedia (it hasn't yet been ported to Python3 because of dependency issues). But this is something that will only be used for unit tests, not at run-time, so having it in a separate module makes sense.