Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- #
- # Copyright (C) 2009 James Bellenger <james@forwardcamegrendel.org>
- #
- # Licensed under GPL version 2. For details of what this means, write to
- # Free Software Foundation, Inc.
- # 51 Franklin St, Fifth Floor,
- # Boston, MA 02110-1301, USA
- #
- # CHANGELOG
- # 0.2.2
- # - Changed API params to make it compatible with 0.12.1
- # - Added --rar option to resolve rar files to its content using unrar binary
- #
- # 0.2.1 by James Bellenger
- VERSION = '0.2.2'
- USAGE = """
- This program turns interesting inotify events into HTTP requests that can trigger a
- mediatomb server to add or remove media.
- Required Parameters:
- -h, --host Hostname of mediatomb daemon. Mediatomb should be running with webui enabled and
- authorization disabled (the default configuration). This parameter is required.
- -w, --watch A directory to watch. Try to avoid specifying overlapping directories
- This parameter is required and can be specified multiple times.
- Optional Parameters:
- -p, --port Port to look for mediatomb web service on. Defaults to 49152.
- -t, --translate A simple regex for translating paths on the file server to a path on the mediatomb machine.
- If this machine sees a file at /export/video/movie.mp4, and mediatomb sees the same file at
- /mnt/video/movie.mp4, you'll want to specify a value of 'export,mnt'. Can be specified
- multiple times.
- -r, --rar Resolve rar filenames to content filenames (Deleting won't work)
- -l, --log Log output to a given file.
- -v, --verbose Print extra debugging information.
- -V, --version Print version number and exit.
- --help Print this message and exit.
- """.strip()
- import getopt
- import os
- import pyinotify
- import re
- import sys
- import urllib
- import logging
- import subprocess as sub
- from xml.dom import minidom
- class Logger:
- log = None
- formatter = logging.Formatter('[%(asctime)s %(levelname)s] %(message)s')
- handler = None
- _instance = None
- def __init__(self):
- self.log = logging.getLogger(sys.argv[0])
- self.handler = logging.StreamHandler()
- self.handler.setFormatter(self.formatter)
- self.log.addHandler(self.handler)
- self.log.setLevel(logging.INFO)
- for level in 'debug', 'info', 'warn', 'error', 'exception', 'fatal', 'setLevel':
- setattr(self, level, getattr(self.log, level))
- def logtofile(self, fname):
- self.log.removeHandler(self.handler)
- self.handler = logging.FileHandler(fname)
- self.handler.setFormatter(self.formatter)
- self.log.addHandler(self.handler)
- def _getinstance(cls):
- if not cls._instance:
- cls._instance = Logger()
- return cls._instance
- getinstance = classmethod(_getinstance)
- log = Logger.getinstance()
- class Mediatomb:
- SID_PATTERN = re.compile(r'sid="(.*?)"', re.MULTILINE)
- REQUEST_FORMAT = "http://%s:%d/content/interface?req_type=%s&return_type=xml"
- host = None
- port = None
- _pcdir = None
- _sid = None
- def __init__(self, host, port):
- self.host = host
- self.port = port
- self._initsession()
- def _initsession(self):
- opener = urllib.URLopener()
- response = self._request('auth', sid='null', action='get_sid')
- if response:
- match = re.search(Mediatomb.SID_PATTERN, response)
- if match:
- self._sid = match.group(1)
- log.info('Created session ' + self._sid)
- return self._sid
- else:
- raise Exception('Could not initialize mediatomb session')
- def _tohex(self, string):
- return ''.join(["%02X" % ord(x) for x in string]).strip().lower()
- def _request(self, req_type, retries=1, **kwargs):
- if 'sid' not in kwargs:
- if not self._sid:
- self._initsession()
- kwargs['sid'] = self._sid
- opener = urllib.URLopener()
- url = Mediatomb.REQUEST_FORMAT % (self.host, self.port, req_type)
- extras = '&'.join([ '%s=%s' % i for i in kwargs.items() ])
- if extras:
- url = '&'.join((url, extras))
- log.debug('opening url: ' + url)
- try:
- response = opener.open(url)
- if response:
- body = response.read()
- log.debug('Response: ' + body)
- if '<redirect>/</redirect>' in body and retries > 0:
- log.info('Expiring stale sid')
- kwargs.pop('sid')
- self._sid = None
- return self._request(req_type, retries-1, **kwargs)
- else:
- return body
- else:
- log.error('No response received')
- except Exception, e:
- log.exception('Could not connect to mediatomb at %s:%d. Are you sure it\'s running?' % (self.host, self.port), e)
- def _initpcdir(self):
- containers = self.containers(0)
- if containers:
- for id, name in containers.items():
- if name == 'PC Directory':
- log.debug('loaded PC Directory id %s' % id)
- self._pcdir = id
- return
- def containers(self, parent):
- response = self._request('containers', parent_id=parent)
- if response:
- dom = minidom.parseString(response)
- containers = dict()
- for container in dom.getElementsByTagName('container'):
- name = container.firstChild.data
- id = container.getAttribute('id')
- if name and id:
- containers[id] = name
- return containers
- def items(self, parent):
- response = self._request('items', parent_id=parent, start=0, count=0)
- if response:
- dom = minidom.parseString(response)
- items = dict()
- for item in dom.getElementsByTagName('item'):
- id = item.getAttribute('id')
- titles = item.getElementsByTagName('title')
- if titles:
- items[id] = titles[-1].firstChild.data
- return items
- def getmtid(self, path):
- if self._pcdir is None:
- self._initpcdir()
- if self._pcdir is None:
- log.error('Is PC Directory disabled in mediatomb?')
- return
- path = path.split(os.path.sep)
- if path[0] == '':
- path.pop(0)
- fname = path.pop()
- parent = self._pcdir
- for p in path:
- for id, name in self.containers(parent).items():
- if name == p:
- parent = id
- break
- if parent != self._pcdir:
- for id, name in self.items(parent).items():
- if name == fname:
- return id
- def removefile(self, path):
- mtid = self.getmtid(path)
- if mtid:
- response = self._request('remove', object_id = mtid)
- if response:
- if '<root success="1"/>' in response:
- log.info('removed ' + path)
- else:
- log.error('Could not understand server response when removing %s. Response:\n%s' % (path, response))
- def addfile(self, path):
- id = self._tohex(path)
- response = self._request('add', object_id = self._tohex(path))
- if response:
- if '<root success="1"/>' in response or 'Adding:' in response:
- log.info('Added file %s' % path)
- else:
- log.error('Could not understand server response when adding %s. Response:\n%s' % (path, response))
- class Monitor:
- EVENT_MASK = pyinotify.IN_CREATE |\
- pyinotify.IN_MOVED_TO |\
- pyinotify.IN_MOVED_FROM |\
- pyinotify.IN_DELETE
- _notify = None
- _mediatomb = None
- _watchmgr = None
- _notifycheck = None
- _notifystop = None
- _eventcheck = None
- translations = None
- def __init__(self, mediatomb, translations):
- # there are different apis between pyinotify 2.5 and 2.4
- if 'WatchManager' in dir(pyinotify):
- # 2.5
- self._watchmgr = pyinotify.WatchManager()
- self._notify = pyinotify.Notifier(self._watchmgr)
- self._notifycheck = self._notify.check_events
- self._notifystop = self._notify.stop
- else:
- # 2.4
- self._notify = pyinotify.SimpleINotify()
- self._watchmgr = self._notify
- self._notifycheck = self._notify.event_check
- self._notifystop = self._notify.close
- self._mediatomb = mediatomb
- self.translations = translations
- self.resolverar = resolverar
- def add_watch(self, path):
- log.info('Adding watch for %s (this can take a while)... ' % path)
- self._watchmgr.add_watch(path, Monitor.EVENT_MASK, self._onevent, True)
- log.info('Watch for %s added.' % path)
- def process(self):
- self._notify.process_events()
- if self._notifycheck(1):
- self._notify.read_events()
- def close(self):
- self._notifystop()
- def _onevent(self, event):
- # IN_IGNORED generated when removing a directory
- if not event.mask & pyinotify.IN_IGNORED:
- if event.path:
- fname = event.path;
- if event.name:
- if self.resolverar:
- fname = os.path.join(fname, event.name)
- p = sub.Popen(['unrar', 'lb', fname], stdout=sub.PIPE,stderr=sub.PIPE)
- fname = p.stdout.readline().rstrip()
- if fname:
- fname = os.path.join(event.path, fname)
- else:
- fname = os.path.join(event.path, event.name)
- else:
- fname = os.path.join(fname, event.name)
- if event.name.startswith('.'):
- log.debug('Ignoring hidden file ' + fname)
- return
- for translation in self.translations:
- fname = re.sub(translation[0], translation[1], fname)
- # if we're processing a directory, just add it it to our watch list
- if event.mask & pyinotify.IN_ISDIR:
- if event.mask & pyinotify.IN_CREATE:
- self._watchmgr.add_watch(fname, Monitor.EVENT_MASK, self._onevent, True)
- log.info('Added new watch directory ' + fname)
- elif event.mask & (pyinotify.IN_DELETE | pyinotify.IN_MOVED_FROM):
- self._mediatomb.removefile(fname)
- else:
- self._mediatomb.addfile(fname)
- if __name__ == '__main__':
- watches = list()
- host = None
- port = 49152
- translations = list()
- resolverar = 0
- if len(sys.argv) > 1:
- opts, args = getopt.gnu_getopt(sys.argv[1:], 'l:t:r:h:p:e:w:vV', ('log=', 'translate=', 'rar', 'host=', 'port=', 'help', 'watch=', 'verbose', 'version'))
- for opt, arg in opts:
- if opt in ('-l', '--log'):
- log.logtofile(arg)
- if opt == '-t' or opt == '--translate':
- targs = arg.split(',')
- if len(targs) != 2:
- print 'Cannot understand translation argument %s' % arg
- sys.exit(1)
- else:
- translations.append(targs)
- if opt in ('-r', '--rar'):
- resolverar = 1
- if opt in ('-h', '--host'):
- host = arg
- if opt in ('-p', '--port'):
- port = int(arg)
- if opt in ('-w', '--watch'):
- watches.append(arg)
- if opt in ('-v', '--verbose'):
- log.setLevel(logging.DEBUG)
- if opt in ('-V', '--version'):
- print 'mtproxy version %s' % VERSION
- sys.exit(0)
- if opt == '--help':
- print USAGE
- sys.exit(0)
- else:
- print USAGE
- sys.exit(1)
- if not host:
- print 'Mediatomb host not specified. Exiting'
- sys.exit(1)
- if not port:
- print 'Mediatomb port not specified. Exiting'
- sys.exit(1)
- if not watches:
- print 'No watches specified. Exiting'
- sys.exit(1)
- log.info('Starting mtproxy')
- mediatomb = Mediatomb(host, port)
- monitor = Monitor(mediatomb, translations)
- for watch in watches:
- monitor.add_watch(watch)
- while True:
- try:
- monitor.process()
- except KeyboardInterrupt:
- log.info('Closing')
- monitor.close()
- break
- except Exception, err:
- print err
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement