Handle URL

This commit is contained in:
Cédric Krier committed 2011-02-15 16:08:52 +01:00
1 parent 73210bee46
commit 915b1ddf2f
8 files changed
+364 -4

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Handle URL
* Add communication between boards
* Added domain inversion feature
* Handle loading attribute on fields
+25 -1
View File
@@ -17,7 +17,7 @@ Synopsis
::
tryton [options]
tryton [options] [url]
After startup, there raises the `login dialog`__ and optionally a
`tips dialog`__.
@@ -48,6 +48,30 @@ Options
-s SERVER, --server=SERVER Specify the server hostname
URL
***
When an url is passed, the client will try to find already running client that
could handle it and send to this one to open the url. If it doesn't find one
then it will start the GUI and open the url itself.
The url schemes are:
`tryton://<hostname>[:<port>]/<database>/model/<model name>[/<id>][;parameters]`
`tryton://<hostname>[:<port>]/<database>/wizard/<wizard name>[;parameters]`
`tryton://<hostname>[:<port>]/<database>/report/<report name>[;parameters]`
where `parameters` are the corresponding fields of actions encoded in
`JSON`_.
.. _JSON: http://en.wikipedia.org/wiki/Json
.. Note:: `model` is for `act_window`
.. Note:: `report` must have at least a data parameter with `ids`, `id` and
`model name`
Overview
********
+6
View File
@@ -98,6 +98,12 @@ SectionIn 1 2 RO
SetOutPath "$INSTDIR\doc"
File /r "doc\*"
;Register URL protocol
WriteRegStr HKCR "tryton" "" "URL:Tryton Protocol"
WriteRegStr HKCR "tryton" "URL Protocol" ""
WriteRegStr HKCR "tryton\DefaultIcon" "" "$INSTDIR\tryton.exe,1"
WriteRegStr HKCR "tryton\shell\open\command" "" '$INSTDIR\tryton.exe "%1"'
;Write the installation path into the registry
WriteRegStr HKCU "Software\tryton-${VERSION}" "" $INSTDIR
WriteRegStr HKLM "Software\tryton-${VERSION}" "" $INSTDIR
+15
View File
@@ -12,6 +12,7 @@ if not (os.name in ('nt', 'mac') or \
(hasattr(os, 'uname') and os.uname()[0] == 'Darwin')):
gtk.gdk.threads_init()
import logging
from urlparse import urlparse
from tryton import version
from tryton import config
@@ -19,6 +20,7 @@ from tryton.config import CONFIG, CURRENT_DIR, PREFIX, PIXMAPS_DIR, \
TRYTON_ICON, get_config_dir
from tryton import translate
from tryton import gui
from tryton.ipc import Client as IPCClient
import traceback
import time
import signal
@@ -29,6 +31,19 @@ class TrytonClient(object):
def __init__(self):
CONFIG.parse()
if CONFIG.arguments:
url, = CONFIG.arguments
urlp = urlparse(url)
if urlp.scheme == 'tryton':
urlp = urlparse('http' + url[6:])
hostname, port = (urlp.netloc.split(':', 1)
+ [CONFIG.defaults['login.port']])[:2]
database, _ = (urlp.path[1:].split('/', 1) + [None])[:2]
if IPCClient(hostname, port, database).write(url):
sys.exit(0)
CONFIG['login.server'] = hostname
CONFIG['login.port'] = port
CONFIG['login.db'] = database
logging.basicConfig()
translate.set_language_direction(CONFIG['client.language_direction'])
translate.setlang(CONFIG['client.lang'])
+6 -2
View File
@@ -90,9 +90,11 @@ class ConfigManager(object):
self.options = {
'login.host': True
}
self.arguments = []
def parse(self):
parser = optparse.OptionParser(version=("Tryton %s" % VERSION))
parser = optparse.OptionParser(version=("Tryton %s" % VERSION),
usage="Usage: %prog [options] [url]")
parser.add_option("-c", "--config", dest="config",
help=_("specify alternate config file"))
parser.add_option("-v", "--verbose", action="store_true",
@@ -109,8 +111,10 @@ class ConfigManager(object):
help=_("specify the server port"))
parser.add_option("-s", "--server", dest="server",
help=_("specify the server hostname"))
opt = parser.parse_args()[0]
opt, self.arguments = parser.parse_args()
if len(self.arguments) > 1:
raise Exception(_('Too much arguments'))
if opt.config and not os.path.isfile(opt.config):
raise Exception(_('File "%s" not found') % (opt.config,))
+129 -1
View File
@@ -3,14 +3,21 @@
import os
import sys
import gettext
import urlparse
from urlparse import urlparse
import urllib
import gobject
import gtk
if sys.version_info < (2, 6):
import simplejson as json
else:
import json
import webbrowser
import tryton.rpc as rpc
from tryton.config import CONFIG, TRYTON_ICON, PIXMAPS_DIR, DATA_DIR, \
get_config_dir
import tryton.common as common
from tryton.action import Action
from tryton.wizard import Wizard
from tryton.gui.window import Window
from tryton.gui.window.preference import Preference
from tryton.gui.window import Limit
@@ -1186,6 +1193,9 @@ class Main(object):
self.menuitem_form.set_sensitive(True)
self.menuitem_plugins.set_sensitive(True)
self.notebook.grab_focus()
if CONFIG.arguments:
url = CONFIG.arguments.pop()
self.open_url(url)
return True
def sig_logout(self, widget, disconnect=True):
@@ -1616,3 +1626,121 @@ class Main(object):
else:
rpc.logout()
Main.get_main().refresh_ssl()
def _open_url(self, url):
url = urllib.unquote(url)
urlp = urlparse(url)
if not urlp.scheme == 'tryton':
return
urlp = urlparse('http' + url[6:])
hostname, port = (urlp.netloc.split(':', 1)
+ [CONFIG.defaults['login.port']])[:2]
database, path = (urlp.path[1:].split('/', 1) + [None])[:2]
if (not path or
hostname != rpc._SOCK.hostname or
int(port) != rpc._SOCK.port or
database != rpc._DATABASE):
return
type_, path = (path.split('/', 1) + [''])[:2]
params = {}
if urlp.params:
try:
params = dict(param.split('=', 1)
for param in urlp.params.split('&'))
except Exception:
return
def open_model(path):
model, path = (path.split('/', 1) + [''])[:2]
if not model:
return
res_id = False
mode = None
view_type = params.get('view_type', 'form')
try:
view_ids = json.loads(params.get('views', 'false'))
limit = json.loads(params.get('limit', 'null'))
auto_refresh = json.loads(params.get('auto_refresh', 'false'))
name = json.loads(params.get('window_name', 'false'))
search_value = json.loads(params.get('search_value', '{}'))
domain = json.loads(params.get('domain', '[]'))
context = json.loads(params.get('context', '{}'))
except ValueError:
return
if path:
try:
res_id = int(path)
except ValueError:
return
mode = ['form', 'tree']
try:
Window.create(view_ids, model, res_id=res_id, domain=domain,
view_type=view_type, window=self.window,
context=context, mode=mode, name=name, limit=limit,
auto_refresh=auto_refresh, search_value=search_value)
except Exception:
return
def open_wizard(wizard):
if not wizard:
return
try:
data = json.loads(params.get('data', '{}'))
direct_print = json.loads(params.get('direct_print', 'false'))
email_print = json.loads(params.get('email_print', 'false'))
email = json.loads(params.get('email', 'null'))
name = json.loads(params.get('name', 'false'))
window = json.loads(params.get('window', 'false'))
context = json.loads(params.get('context', '{}'))
except ValueError:
return
try:
if window:
Window.create_wizard(wizard, data, self.window,
direct_print=direct_print, email_print=email_print,
email=email, name=name, context=context)
else:
Wizard.execute(wizard, data, self.window,
direct_print=direct_print, email_print=email_print,
email=email, context=context)
except Exception:
return
def open_report(report):
if not report:
return
try:
data = json.loads(params.get('data'))
direct_print = json.loads(params.get('direct_print', 'false'))
email_print = json.loads(params.get('email_print', 'false'))
email = json.loads(params.get('email', 'null'))
name = json.loads(params.get('name', 'false'))
context = json.loads(params.get('context', '{}'))
except ValueError:
return
try:
Action.exec_report(report, data, self.window,
direct_print=direct_print, email_print=email_print,
email=email, context=context)
except Exception:
return
def open_url():
try:
url = json.loads(params.get('url', 'false'))
except ValueError:
return
if url:
webbrowser.open(url, new=2)
if type_ == 'model':
open_model(path)
elif type_ == 'wizard':
open_wizard(path)
elif type_ == 'report':
open_report(path)
elif type_ == 'url':
open_url()
def open_url(self, url):
gobject.idle_add(self._open_url, url)
+178
View File
@@ -0,0 +1,178 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
"""
Inter-Process Communication
"""
import tempfile
import os
import threading
import select
import time
from tryton.config import get_config_dir
__all__ = ['Server', 'Client']
class IPCServer(object):
thread = None
running = None
instance = None
def __init__(self, hostname, port, database):
self.hostname = hostname
self.port = port
self.database = database
self.config = os.path.join(get_config_dir(), '%s@%s@%s' %
(self.hostname, self.port, self.database))
self.tmpdir = tempfile.mkdtemp(prefix='.tryton')
Server.instance = self
def setup(self):
raise NotImplemented
def run(self):
self.setup()
self.running = True
self.thread = threading.Thread(target=self._read)
self.thread.start()
def clean(self):
raise NotImplemented
def stop(self):
self.running = False
self.thread.join()
self.thread = None
self.clean()
Server.instance = None
def _read(self):
raise NotImplemented
class FileServer(IPCServer):
def setup(self):
config = open(self.config, 'w')
print >> config, self.tmpdir
def clean(self):
try:
os.remove(self.config)
os.rmdir(self.tmpdir)
except OSError:
pass
def _read(self):
to_remove = set()
while self.running:
for filename in os.listdir(self.tmpdir):
path = os.path.join(self.tmpdir, filename)
if not os.path.isfile(path):
continue
if path in to_remove:
continue
try:
data = open(path, 'r').readline()
except IOError:
pass
if data and data[-1] != '\n':
continue
to_remove.add(path)
if data:
from tryton.gui.main import Main
Main.get_main().open_url(data[:-1])
if not os.path.exists(self.config):
self.setup()
for path in to_remove.copy():
try:
os.remove(path)
except Exception:
continue
to_remove.remove(path)
time.sleep(1)
class FIFOServer(IPCServer):
def setup(self):
self.filename = os.path.join(self.tmpdir, 'Socket')
os.mkfifo(self.filename, 0600)
if os.path.lexists(self.config):
os.remove(self.config)
os.symlink(self.filename, self.config)
def clean(self):
try:
os.remove(self.config)
os.rmdir(self.tmpdir)
except OSError:
pass
def _read(self):
fifo = os.fdopen(os.open(self.filename, os.O_RDONLY|os.O_NONBLOCK))
data = ''
while self.running:
rlist, _, _ = select.select([fifo], [], [], 1)
if rlist:
try:
data += fifo.readline()
except IOError:
pass
if data and data[-1] != '\n':
continue
if data:
from tryton.gui.main import Main
Main.get_main().open_url(data.strip())
data = ''
if not os.path.lexists(self.config):
os.symlink(self.filename, self.config)
class IPCClient(object):
def __init__(self, hostname, port, database):
self.hostname = hostname
self.port = port
self.database = database
self.filename = os.path.join(get_config_dir(),
'%s@%s@%s' % (hostname, port, database))
def write(self, message):
raise NotImplemented
class FileClient(IPCClient):
def __init__(self, hostname, port, database):
super(FileClient, self).__init__(hostname, port, database)
def write(self, message):
if not os.path.exists(self.filename):
return False
tmpdir = open(self.filename, 'r').readline().strip()
_, tmpfile = tempfile.mkstemp(dir=tmpdir, text=True)
tmpfile = open(tmpfile, 'w')
print >> tmpfile, message
tmpfile.close()
return True
class FIFOClient(IPCClient):
def __init__(self, hostname, port, database):
super(FIFOClient, self).__init__(hostname, port, database)
def write(self, message):
if not os.path.lexists(self.filename):
return False
fifo = open(self.filename, 'w')
print >> fifo, message
return True
if hasattr(os, 'mkfifo'):
Server = FIFOServer
Client = FIFOClient
else:
Server = FileServer
Client = FileClient
+4
View File
@@ -7,6 +7,7 @@ import os
from threading import Semaphore
from tryton.fingerprints import Fingerprints
from tryton.config import get_config_dir
from tryton.ipc import Server as IPCServer
_SOCK = None
_USER = None
@@ -154,10 +155,13 @@ def login(username, password, host, port, database):
_DATABASE = database
SECURE = _SOCK.ssl
context_reload()
IPCServer(host, port, database).run()
return 1
def logout():
global _SOCK, _USER, _USERNAME, _SESSION, _DATABASE, _VIEW_CACHE, SECURE
if IPCServer.instance:
IPCServer.instance.stop()
if _SOCK and _USER:
try:
_SEMAPHORE.acquire()