41
41
# Common
42
42
#----------------------------------------------------------
43
43
44
+ class AutoReload (object ):
45
+ def __init__ (self ):
46
+ self .files = {}
47
+ import pyinotify
48
+ class EventHandler (pyinotify .ProcessEvent ):
49
+ def __init__ (self , autoreload ):
50
+ self .autoreload = autoreload
51
+
52
+ def process_IN_CREATE (self , event ):
53
+ _logger .debug ('File created: %s' , event .pathname )
54
+ self .autoreload .files [event .pathname ] = 1
55
+
56
+ def process_IN_MODIFY (self , event ):
57
+ _logger .debug ('File modified: %s' , event .pathname )
58
+ self .autoreload .files [event .pathname ] = 1
59
+
60
+ self .wm = pyinotify .WatchManager ()
61
+ self .handler = EventHandler (self )
62
+ self .notifier = pyinotify .Notifier (self .wm , self .handler , timeout = 0 )
63
+ mask = pyinotify .IN_MODIFY | pyinotify .IN_CREATE # IN_MOVED_FROM, IN_MOVED_TO ?
64
+ for path in openerp .tools .config .options ["addons_path" ].split (',' ):
65
+ _logger .info ('Watching addons folder %s' , path )
66
+ self .wm .add_watch (path , mask , rec = True )
67
+
68
+ def process_data (self , touched_files ):
69
+ # pyinotify notifier + fs modiciation tracker
70
+ from openerp .modules .module import load_information_from_description_file as load_manifest
71
+ addons_path = openerp .tools .config .options ["addons_path" ].split (',' )
72
+ registries = openerp .modules .registry .RegistryManager .registries
73
+ keys = ['data' , 'demo' , 'test' , 'init_xml' , 'update_xml' , 'demo_xml' ]
74
+ # This will only work for loaded registies, so we have to use -d <database>
75
+ # al: proposed to move this code in the registry manager so it can be lazy
76
+ for db_name , registry in registries .items ():
77
+ cr = registry .db .cursor ()
78
+ try :
79
+ for tfile in touched_files :
80
+ for path in addons_path :
81
+ if tfile .startswith (path ):
82
+ # find out wich addons path the file belongs to
83
+ # and extract it's module name
84
+ right = tfile [len (path ) + 1 :].split ('/' )
85
+ if len (right ) < 2 :
86
+ continue
87
+ module = right [0 ]
88
+ relname = "/" .join (right [1 :])
89
+ domain = [('name' , '=' , module ), ('state' , 'in' , ['installed' , 'to upgrade' ])]
90
+ if registry .get ('ir.module.module' ).search (cr , openerp .SUPERUSER_ID , domain ):
91
+ manifest = load_manifest (module )
92
+ kind = [key for key in keys if relname in manifest [key ]]
93
+ if kind :
94
+ _logger .info ('Updating changed xml file: %s' , tfile )
95
+ idref = {}
96
+ openerp .tools .convert_file (cr , module , relname , idref , mode = 'update' , kind = kind [0 ])
97
+ cr .commit ()
98
+ except Exception ,e :
99
+ _logger .exception (e )
100
+ finally :
101
+ cr .close ()
102
+
103
+ def process_python (self , files ):
104
+ # process python changes
105
+ py_files = [i for i in files if i .endswith ('.py' )]
106
+ py_errors = []
107
+ # TODO keep python errors until they are ok
108
+ if py_files :
109
+ for i in py_files :
110
+ try :
111
+ source = open (i , 'rb' ).read () + '\n '
112
+ compile (source , i , 'exec' )
113
+ except SyntaxError :
114
+ py_errors .append (i )
115
+ if py_errors :
116
+ _logger .info ('autoreload: python code change detected, errors found' )
117
+ for i in py_errors :
118
+ _logger .info ('autoreload: SyntaxError %s' ,i )
119
+ else :
120
+ _logger .info ('autoreload: python code updated, autoreload activated' )
121
+ restart_server ()
122
+
123
+ def check (self ):
124
+ # Check if some files have been touched in the addons path.
125
+ # If true, check if the touched file belongs to an installed module
126
+ # in any of the database used in the registry manager.
127
+ while self .notifier .check_events (0 ):
128
+ self .notifier .read_events ()
129
+ self .notifier .process_events ()
130
+ l = self .files .keys ()
131
+ self .files .clear ()
132
+ self .process_data (l )
133
+ self .process_python (l )
134
+
44
135
class CommonServer (object ):
45
136
def __init__ (self , app ):
46
137
# TODO Change the xmlrpc_* options to http_*
@@ -101,8 +192,8 @@ def __init__(self, app):
101
192
self .quit_signals_received = 0
102
193
103
194
#self.socket = None
104
- #self.queue = []
105
195
self .httpd = None
196
+ self .autoreload = None
106
197
107
198
def signal_handler (self , sig , frame ):
108
199
if sig in [signal .SIGINT ,signal .SIGTERM ]:
@@ -152,7 +243,11 @@ def target():
152
243
_logger .debug ("cron%d started!" % i )
153
244
154
245
def http_thread (self ):
155
- self .httpd = werkzeug .serving .make_server (self .interface , self .port , self .app , threaded = True )
246
+ def app (e ,s ):
247
+ if self .autoreload :
248
+ self .autoreload .check ()
249
+ return self .app (e ,s )
250
+ self .httpd = werkzeug .serving .make_server (self .interface , self .port , app , threaded = True )
156
251
self .httpd .serve_forever ()
157
252
158
253
def http_spawn (self ):
@@ -172,6 +267,7 @@ def start(self):
172
267
win32api .SetConsoleCtrlHandler (lambda sig : signal_handler (sig , None ), 1 )
173
268
self .cron_spawn ()
174
269
self .http_spawn ()
270
+ self .autoreload = AutoReload ()
175
271
176
272
def stop (self ):
177
273
""" Shutdown the WSGI server. Wait for non deamon threads.
@@ -189,7 +285,7 @@ def stop(self):
189
285
_logger .debug ('current thread: %r' , me )
190
286
for thread in threading .enumerate ():
191
287
_logger .debug ('process %r (%r)' , thread , thread .isDaemon ())
192
- if thread != me and not thread .isDaemon () and thread .ident != main_thread_id :
288
+ if thread != me and not thread .isDaemon () and thread .ident != self . main_thread_id :
193
289
while thread .isAlive ():
194
290
_logger .debug ('join and sleep' )
195
291
# Need a busyloop here as thread.join() masks signals
@@ -262,11 +358,11 @@ def run(self):
262
358
# Prefork
263
359
#----------------------------------------------------------
264
360
265
- class Multicorn (CommonServer ):
361
+ class PreforkServer (CommonServer ):
266
362
""" Multiprocessing inspired by (g)unicorn.
267
- Multicorn currently uses accept(2) as dispatching method between workers
268
- but we plan to replace it by a more intelligent dispatcher to will parse
269
- the first HTTP request line.
363
+ PreforkServer (aka Multicorn) currently uses accept(2) as dispatching
364
+ method between workers but we plan to replace it by a more intelligent
365
+ dispatcher to will parse the first HTTP request line.
270
366
"""
271
367
def __init__ (self , app ):
272
368
# config
@@ -617,12 +713,12 @@ class WorkerBaseWSGIServer(werkzeug.serving.BaseWSGIServer):
617
713
def __init__ (self , app ):
618
714
werkzeug .serving .BaseWSGIServer .__init__ (self , "1" , "1" , app )
619
715
def server_bind (self ):
620
- # we dont bind beause we use the listen socket of Multicorn #socket
716
+ # we dont bind beause we use the listen socket of PreforkServer #socket
621
717
# instead we close the socket
622
718
if self .socket :
623
719
self .socket .close ()
624
720
def server_activate (self ):
625
- # dont listen as we use Multicorn #socket
721
+ # dont listen as we use PreforkServer #socket
626
722
pass
627
723
628
724
class WorkerCron (Worker ):
@@ -723,10 +819,11 @@ def _reexec():
723
819
def start ():
724
820
""" Start the openerp http server and cron processor.
725
821
"""
822
+ global server
726
823
load_server_wide_modules ()
727
824
if config ['workers' ]:
728
825
openerp .multi_process = True
729
- server = Multicorn (openerp .service .wsgi_server .application )
826
+ server = PreforkServer (openerp .service .wsgi_server .application )
730
827
elif openerp .evented :
731
828
server = GeventServer (openerp .service .wsgi_server .application )
732
829
else :
0 commit comments