| Trees | Indices | Help |
|
|---|
|
|
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 # Moovida - Home multimedia server
4 # Copyright (C) 2007-2009 Fluendo Embedded S.L. (www.fluendo.com).
5 # All rights reserved.
6 #
7 # This file is available under one of two license agreements.
8 #
9 # This file is licensed under the GPL version 3.
10 # See "LICENSE.GPL" in the root of this distribution including a special
11 # exception to use Moovida with Fluendo's plugins.
12 #
13 # The GPL part of Moovida is also available under a commercial licensing
14 # agreement from Fluendo.
15 # See "LICENSE.Moovida" in the root directory of this distribution package
16 # for details on that license.
17 #
18 # Author: Alessandro Decina <alessandro@fluendo.com>
19
20 import os
21 import pkg_resources
22 import sys
23 from distutils.version import LooseVersion
24
25
26 elisa_local_directory = os.path.join(os.path.expanduser('~'), '.moovida')
27 if not os.path.exists(elisa_local_directory):
28 os.makedirs(elisa_local_directory)
29
30
31 plugin_directories = [os.path.join(elisa_local_directory, 'plugins')]
32 plugin_path_environment = 'ELISA_PLUGIN_PATH'
33 if plugin_path_environment in os.environ:
34 plugin_directories += \
35 os.environ[plugin_path_environment].split(os.path.pathsep)
36
37
39 # Relative paths we set when running under windows. If you are the lucky
40 # maintainer of d-dependencies these days, you probably want to keep this
41 # part updated.
42 win_pgm_path = r'deps\lib\pigment-0.3'
43 win_pixbuf_module_file = r'etc\gtk-2.0\gdk-pixbuf.loaders'
44 win_library_paths = ['.',
45 r'deps\bin',
46 r'deps\lib\site-packages\pywin32_system32']
47 win_python_paths = ['elisa-core',
48 r'deps\lib',
49 r'deps\lib\site-packages',
50 r'deps\lib\site-packages\gtk-2.0',
51 r'deps\lib\site-packages\gtk-2.0\\gobject',
52 r'deps\lib\site-packages\win32',
53 r'deps\lib\site-packages\win32com',
54 r'deps\lib\site-packages\PIL',
55 r'deps\lib\site-packages\gst-0.10',
56 r'deps\lib\site-packages\win32\\lib',
57 r'deps\libsite-packages\setuptools-0.6c8-py2.5.egg',
58 r'deps\bin\DLLs',
59 r'deps\lib\site-packages\pywin32_system32']
60
62 """
63 --> This is where you should start reading the source code <--
64
65 The main entry point is split in two parts: main_before_voodoo() and
66 main_after_voodoo().
67
68 The first function does all kinds of black magic to try and load the
69 most recent version of elisa.core. It should be AS MINIMAL AS IT CAN BE
70 and SHOULD NEVER BE MODIFIED. When we ship a new version of the core in
71 ~/.moovida/plugins, an OLD VERSION OF THE FUNCTION (the one installed
72 in a systemwide path) WILL BE EXECUTED anyway.
73
74 The second function does the actual initialization. If
75 main_before_voodoo() is done right (as we're sure it is since I wrote
76 it), the latest available version of main_after_voodoo() will be
77 executed.
78
79 Yes, that's right. I'm awesome.
80 """
81 self.main_before_voodoo()
82 # from now on it's safe to import stuff
83 return self.main_after_voodoo(argv)
84
86 # See http://www.py2exe.org/index.cgi/StderrLog
87 log_file = os.path.join(elisa_local_directory, 'moovida.log')
88 sys.stdout = sys.stderr = open(log_file, 'w')
89
91 if hasattr(sys, 'frozen'):
92 if not sys.executable.endswith('moovida_console.exe'):
93 self.setup_win_output_redirection()
94
95 # delete GST_PLUGIN_PATH as it can conflict with our own plugins
96 environment = self.get_environment()
97 if environment.get('GST_PLUGIN_PATH', ''):
98 environment['GST_PLUGIN_PATH'] = ''
99
100 prefix = self.get_win_prefix()
101
102 # set PATH as soon as possible as we import
103 # twisted.internet.glib2reactor that glib and other dlls
104 library_paths = [os.path.join(prefix, entry)
105 for entry in self.win_library_paths]
106 self.add_environment_path('PATH', library_paths, prepend = True)
107
108 # FIXME: python in the build system is BROKEN so we have to do this. We
109 # REALLY need to fix it, but it's not going to happen anytime soon so...
110 python_paths = [os.path.join(prefix, entry)
111 for entry in self.win_python_paths]
112 sys.path.extend(python_paths)
113
115 import elisa
116 import elisa.core
117 import elisa.core.launcher
118 import elisa.plugins
119
120 if self.get_platform() == 'Windows':
121 self.set_win_environment_pre_voodoo()
122
123 launcher_version_info = elisa.core.version_info
124 self.info('Launcher core version: %s' % elisa.core.__version__)
125
126 # get the set of elisa.* modules imported at this time
127 our_modules = set([module for module in sys.modules.itervalues()
128 if module is not None and module.__name__.startswith('elisa')])
129
130 expected = set([elisa, elisa.core, elisa.core.launcher, elisa.plugins])
131
132 unexpected = our_modules - expected
133 if unexpected:
134 self.error('WARNING: some unexpected modules are loaded: %s'
135 % (' '.join([module.__name__ for module in unexpected])))
136
137 requirement = pkg_resources.Requirement.parse('elisa')
138 core_dist = pkg_resources.working_set.find(requirement)
139
140 working_set = pkg_resources.working_set
141
142 # remove the core distribution from the working_set
143
144 # what we want to do here is this:
145 # working_set.entry_keys[core_dist.location].remove(core_dist.key)
146 # unfortunately some versions of pkg_resources don't normalize
147 # path entries so the same distribution could be under different paths
148 entries = [(path_entry, keys)
149 for path_entry, keys in working_set.entry_keys.iteritems()
150 if core_dist.key in keys]
151 for entry, keys in entries:
152 keys.remove(core_dist.key)
153
154 del working_set.by_key[core_dist.key]
155
156 # empty elisa.__path__, it will be populated again when we add again the
157 # core to the working_set.
158 # NOTE: when you do:
159 # python setup.py install --root=/ --single-version-externally-managed
160 # which is what packagers are doing (god forbid them), namespaces are
161 # not used so we have to put back elisa.__path__ ourselves
162 old_path, elisa.__path__ = list(elisa.__path__), []
163
164 env = pkg_resources.Environment(plugin_directories)
165 distributions, errors = pkg_resources.working_set.find_plugins(env)
166 new_core_dist = None
167 for dist in distributions:
168 if dist.project_name == 'elisa':
169 # Is this core more recent than the current one?
170 if LooseVersion(dist.version) > LooseVersion(core_dist.version):
171 new_core_dist = dist
172 break
173
174 if new_core_dist is None:
175 # oh well, put back the old one
176 working_set.add(core_dist)
177 else:
178 # found a new core
179 working_set.add(new_core_dist)
180
181 if not elisa.__path__:
182 # no namespace packages? put back the old path
183 elisa.__path__ = old_path
184
185 # we need to install the glib2reactor before importing
186 # twisted.python.rebuild else it will load another reactor
187 import gobject
188 gobject.threads_init()
189 from twisted.internet import glib2reactor
190 glib2reactor.install()
191
192 from twisted.python.rebuild import rebuild
193 # reload this very same code...
194 rebuild(elisa.core)
195 rebuild(elisa.core.launcher)
196 # BAM.
197
198 self.info('Current core version: %s' % elisa.core.__version__)
199
200 current_version_info = elisa.core.version_info
201 if current_version_info < launcher_version_info:
202 # THIS SHOULD NEVER HAPPEN
203 self.error('PANIC! Current core older than the previous?\n'
204 'Expect massive breakage.')
205
206 # oh well.. let's go on and give it a shot anyway...
207
209 if self.get_platform() == 'Windows':
210 self.set_win_environment_after_voodoo()
211
212 from elisa.core.options import Options
213 from twisted.python.usage import UsageError
214
215 # This part should probably be moved to Application. Since I don't want
216 # to touch Application now, leave it as something for another day (and
217 # another dev).
218 self.options = Options()
219 try:
220 self.options.parseOptions(argv[1:])
221 except UsageError, errortext:
222 self.error('%s: %s' % (argv[0], errortext))
223 self.error('%s: Try --help for usage details.' % (argv[0]))
224 sys.exit(1)
225
226 if self.poke_running_instance():
227 self.info('An instance of Moovida is already running, exiting.')
228 self.exit(2)
229
230 self.run_application()
231
233 prefix = self.get_win_prefix()
234 environment = self.get_environment()
235
236 # note we use add_environment_path and not set_environment to allow
237 # developers to override our settings, which is what we usually do when
238 # running elisa uninstalled in windows
239 pgm_plugin_path = os.path.join(prefix, self.win_pgm_path)
240 self.add_environment_path('PGM_PLUGIN_PATH', [pgm_plugin_path])
241
242 # set GDK_PIXBUF_MODULE_FILE if it isn't set yet
243 pixbuf_loaders = os.path.join(prefix, 'deps',
244 'etc', 'gtk-2.0', 'gdk-pixbuf.loaders')
245 if not environment.get('GDK_PIXBUF_MODULE_FILE', ''):
246 environment['GDK_PIXBUF_MODULE_FILE'] = pixbuf_loaders
247
249 if self.get_platform() == 'Windows':
250 res = self.poke_win_running_instance()
251 else:
252 res = self.poke_unix_running_instance()
253
254 return res
255
257 try:
258 import dbus
259 import dbus.mainloop.glib
260 except ImportError:
261 # can't detect if an instance is running
262 return False
263
264 dbus.set_default_main_loop(dbus.mainloop.glib.DBusGMainLoop())
265 bus = dbus.SessionBus()
266 daemon = bus.get_object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH)
267 daemon_iface = dbus.Interface(daemon, dbus.BUS_DAEMON_IFACE)
268
269 if self.options.get('force-startup', False):
270 return
271
272 if 'com.fluendo.Elisa' not in daemon_iface.ListNames():
273 return False
274
275 if len(self.options['files']) > 0:
276 bus = dbus.SessionBus()
277 file_player = bus.get_object('com.fluendo.Elisa',
278 '/com/fluendo/Elisa/Plugins/Poblesec/FilePlayer')
279 file_player_iface = dbus.Interface(file_player,
280 'com.fluendo.Elisa.Plugins.Poblesec.FilePlayer')
281 file_player_iface.play_file(self.options['files'])
282
283 # Set focus to the window
284 frontend = bus.get_object('com.fluendo.Elisa',
285 '/com/fluendo/Elisa/Plugins/Pigment/Frontend')
286 frontend_iface = dbus.Interface(frontend,
287 'com.fluendo.Elisa.Plugins.Pigment.Frontend')
288 frontend_iface.show()
289
290 return True
291
293 from win32gui import FindWindow
294 from win32api import SendMessage
295 import pywintypes
296 from ctypes import Structure, POINTER, byref, c_void_p, c_char, sizeof, cast
297 from ctypes.wintypes import ULONG
298 from elisa.core.utils.mswin32.structures import COPYDATASTRUCT
299
300 from win32con import WM_COPYDATA
301
302 try:
303 handle = FindWindow("PIGMENT_CLASS", "Moovida Media Center")
304 except pywintypes.error:
305 # As stated in the MSDN docs on:
306 # http://msdn.microsoft.com/en-us/library/ms633499.aspx
307 # FindWindow call should return a NULL handle if the
308 # window wasn't found but it's not the case on windows < 7
309 return False
310
311 # on windows7 (experimental, build 7100), FindWindow doesn't
312 # raise an exception but returns an NULL handle if the window
313 # wasn't found, as correctly stated in the above MSDN link.
314 if handle == 0:
315 return False
316
317 if len(self.options['files']) > 0:
318 # We use '|' as a file separator
319 command_line = '|'.join(self.options['files'])
320
321 class COMMANDLINEDATA(Structure):
322 _fields_ = [("data", c_char * len(command_line))]
323
324 commandlinedata = COMMANDLINEDATA()
325 commandlinedata.data = command_line
326 copydata = COPYDATASTRUCT()
327 COMMAND_LINE = POINTER(ULONG)()
328 COMMAND_LINE.value = 1
329 copydata.dwData = COMMAND_LINE
330 copydata.cbData = sizeof(commandlinedata)
331 copydata.lpData = cast(byref(commandlinedata), c_void_p)
332 SendMessage(handle, WM_COPYDATA, 0, copydata)
333
334 return True
335
336 # some trivial utility functions used to make mocking in tests easier
338 print message
339
341 print >> sys.stderr, message
342
346
349
351 # assume we're running with a py2exe launcher in windows
352 return os.path.dirname(sys.executable)
353
355 environment = self.get_environment()
356 value = environment.get(variable, '').split(os.pathsep)
357 if prepend:
358 value = entries + value
359 else:
360 value.extend(entries)
361 environment[variable] = os.pathsep.join(value)
362
364 # FIXME: this should all be put in Application
365 from elisa.core.application import Application
366 from elisa.core import common
367 from twisted.internet import reactor
368
369 application = Application(self.options)
370 common.set_application(application)
371
372
373 def start_app():
374 def initialize_done(result):
375 return application.start()
376
377 def initialize_failure(failure):
378 print 'Moovida failed to initialize:', failure
379 reactor.stop()
380
381 dfr = application.initialize()
382 dfr.addCallbacks(initialize_done, initialize_failure)
383
384 reactor.callWhenRunning(start_app)
385
386 # this could lead to an ugly "'NoneType' object is
387 # unsubscriptable" in twisted, when the reactor gets killed from
388 # the outside. Bug is reported and fixed in twisted-svn/trunk:
389 # http://twistedmatrix.com/trac/ticket/2265
390 reactor.addSystemEventTrigger('before', 'shutdown', application.stop)
391 reactor.run()
392
394 sys.exit(code)
395
396
399
400
401 if __name__ == '__main__':
402 main()
403
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Dec 1 10:55:55 2009 | http://epydoc.sourceforge.net |