Handle URL
This commit is contained in:
1 parent
73210bee46
commit
915b1ddf2f
8 files changed
+364
-4
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Handle URL
|
||||
* Add communication between boards
|
||||
* Added domain inversion feature
|
||||
* Handle loading attribute on fields
|
||||
|
||||
+25
-1
@@ -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
|
||||
********
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user