123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- #!/usr/bin/python
- #
- # Plex Extension Framework
- # Copyright (C) 2008-2012 Plex, Inc. (James Clarke, Elan Feingold). All Rights Reserved.
- #
-
- ## CONFIGURE THE PYTHON ENVIRONMENT ##
-
- import sys
- reload(sys)
- sys.setdefaultencoding('utf-8')
-
- import subsystem
-
- import os
- import config
-
- if sys.platform == "win32":
- # This is needed to ensure binary data transfer over stdio between PMS and plugins
- import msvcrt
- msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
- msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
-
- # This is needed to prevent explosions when there's a system Python installed whose libraries conflict with our own
- if 'PLEXLOCALAPPDATA' in os.environ:
- key = 'PLEXLOCALAPPDATA'
- else:
- key = 'LOCALAPPDATA'
- ourlocalappdata = os.path.join(os.environ[key], 'Plex Media Server')
- for x in [x for x in sys.path if sys.prefix.lower() not in x.lower() and ourlocalappdata.lower() not in x.lower()]:
- sys.path.remove(x)
- else:
- import traceback
- import sys
- def dumpstacks(signal, frame):
- code = []
- for threadId, stack in sys._current_frames().items():
- code.append("\n# Thread: %d" % (threadId))
- for filename, lineno, name, line in traceback.extract_stack(stack):
- code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
- if line:
- code.append(" %s" % (line.strip()))
- print "\n".join(code)
-
- import signal
- signal.signal(signal.SIGUSR1, dumpstacks)
-
- PYTHON_DIR = os.path.dirname(os.path.abspath(sys.argv[0]))
- FRAMEWORK_DIR = os.path.abspath(os.path.join(PYTHON_DIR, '..'))
-
- # Redirect stdout to stderr
- sys.stdout = sys.stderr
-
- if sys.platform == "win32":
- os_name = "Windows"
- cpu_name = "i386"
- #TODO - support Windows x64 (Win64)
- else:
- uname = os.uname()
- os_name = uname[0]
- cpu_name = uname[4]
-
- mapped_cpu = config.cpu_map.get(cpu_name, cpu_name)
-
- # Special case for Linux/x64 (really should be special case for OS X...)
- if os_name == 'Linux' and cpu_name == 'x86_64':
- mapped_cpu = 'x86_64'
-
- PLATFORM_DIR = os.path.abspath(os.path.join(FRAMEWORK_DIR, '..', '..', "Platforms", config.os_map[os_name], mapped_cpu))
- SHARED_DIR = os.path.abspath(os.path.join(FRAMEWORK_DIR, '..', '..', "Platforms", "Shared"))
- #TODO: Check for errors
-
- # If the environment variable PLEXBUNDLEDEXTS is set, this indicates a newer
- # server which comes bundled with its own binaries so we'll skip this step
- # completely.
- #
- if 'PLEXBUNDLEDEXTS' not in os.environ:
- lib_path = os.path.join(PLATFORM_DIR, "Libraries")
- # The binary lib path goes at the end on non-Mac platforms, because binary
- # extensions should be picked up from the PMS Exts directory first.
- #
- if sys.platform != "darwin":
- sys.path.append(lib_path)
- else:
- sys.path.insert(0, lib_path)
-
- # Insert the shared (Python-only) libraries.
- sys.path.insert(0, os.path.join(SHARED_DIR, "Libraries"))
-
- ## LOAD AND CONFIGURE THE FRAMEWORK ##
-
- import Framework
- import Framework.constants as const
- from optparse import OptionParser
-
- parser = OptionParser()
- parser.add_option("-i", "--interface", dest="interface", default=config.default_interface)
- parser.add_option("-q", "--quiet", action="store_false", dest="verbose", default=True)
- parser.add_option("-p", "--socket-interface-port", dest="socket_interface_port", default=config.socket_interface_port)
- parser.add_option("-s", "--server-version", dest="server_version")
- parser.add_option("-d", "--daemon", dest="daemon_command")
- parser.add_option("-P", "--pid-file", dest="pid_file")
- parser.add_option("-l", "--log-file", dest="log_file")
- parser.add_option("-c", "--config-file", dest="config_file")
- (options, args) = parser.parse_args()
-
- bundle_path = args[0]
-
- del parser
- del OptionParser
-
- # Select the interface class to use
- if options.interface == const.interface.pipe:
- interface_class = Framework.interfaces.PipeInterface
- elif options.interface == const.interface.socket:
- interface_class = Framework.interfaces.SocketInterface
- if int(options.socket_interface_port) != config.socket_interface_port:
- config.socket_interface_port = int(options.socket_interface_port)
- else:
- #TODO: Error info - no matching interface found
- sys.stderr.write('No matching interface found.\n')
- sys.exit(1)
-
- if options.server_version != None:
- config.server_version = options.server_version
-
- # Configure the log_dir, if one was given
- if options.log_file:
- config.log_file = os.path.abspath(options.log_file)
-
- # Configure the pid file, if one was given
- if options.pid_file:
- config.pid_file = os.path.abspath(options.pid_file)
-
- # Load the config file if one was provided
- if options.config_file:
- import simplejson
- f = open(options.config_file, 'r')
- json_config = simplejson.load(f)
- f.close()
- for key in json_config:
- setattr(config, key, json_config[key])
-
- def run(daemonized=False):
- # Copy the damonized attribute into config
- setattr(config, 'daemonized', daemonized)
-
- # Create a core object for the plug-in bundle
- core = Framework.core.FrameworkCore(bundle_path, FRAMEWORK_DIR, config)
-
- # Try to load the plug-in code
- if not core.load_code():
- sys.stderr.write('Error loading bundle code.\n')
- sys.exit(2)
-
- # Create an instance of the selected interface
- interface = interface_class(core)
-
- # Try to start the core
- if not core.start():
- sys.stderr.write('Error starting framework core for %s.\n' % bundle_path)
- sys.exit(3)
-
- # Start listening on the interface
- interface.listen(daemonized)
-
- # If running normally, start the core in the current process
- if options.daemon_command == None:
- run()
-
- # If issued a daemon command, check what we're supposed to do
- else:
- import plistlib, daemon, tempfile
-
- # Read the plist to get the identifier
- plist_path = os.path.join(bundle_path, 'Contents', 'Info.plist')
- ident = plistlib.readPlist(plist_path)['CFBundleIdentifier']
-
- class PluginDaemon(daemon.Daemon):
- def run(self):
- run(True)
-
- # Make sure we have a pid file for this daemon
- pid_config = dict(identifier = ident, port = config.socket_interface_port)
- pid_dir = os.path.join(config.root_path, config.pid_files_dir)
-
- # Create the pid files dir if it doesn't exist
- if not os.path.exists(pid_dir):
- try:
- os.makedirs(pid_dir)
- except:
- pass
-
- if not config.pid_file:
- pid_file = os.path.join(pid_dir, '%(identifier)s.%(port)d.pid' % pid_config)
- else:
- pid_file = config.pid_file % pid_config
-
- # Create the daemon object
- d = PluginDaemon(pid_file)
-
- # Decide which action to perform
- if options.daemon_command == 'start':
- d.start()
- elif options.daemon_command == 'restart':
- d.restart()
- elif options.daemon_command == 'stop':
- # Wait up to 60 seconds for the interface to stop
- port = 0
- try:
- port = pid_config['port']
- urllib2.urlopen('http://127.0.0.1:%d/:/shutdownInterface' % port, timeout=60)
- except:
- print "Error shutting down interface on port %d" % port
- # Kill the daemon process
- d.stop()
- else:
- print "Unknown command '%s'" % options.daemon_command
|