<commit_before>#
# Copyright (c) 2004 Specifix, Inc.
# All rights reserved
#

import util
import re
import os
import policy
import log

"""
Module used by recipes to modify the state of the installed %(destdir)s
Classes from this module are not used directly; instead, they are used
through eponymous interfaces in recipe.
"""

class SanitizeSonames(policy.Policy):
    """
    make sure that .so -> SONAME -> fullname
    """
    def do(self):
	pass

class RemoveExtraLibs(policy.Policy):
    """
    Kill .la files and any other similar garbage
    """
    invariantinclusions = [
	'\.la$',
	'%(libdir)s/python.*/site-packages/.*.a$'
    ]

    def doFile(self, path):
	util.remove(self.macros['destdir']+path)

class FixupMultilibPaths(policy.Policy):
    """
    Fix up (and warn) when programs do not know about %(lib) and they
    are supposed to be installing to lib64
    FIXME: must test when we have a multilib platform!
    """
    invariantinclusions = [
	'*.\.(so.*|a)$',
    ]

    def __init__(self, *args, **keywords):
	self.dirmap = {
	    '/lib':            '/%(lib)s',
	    '%(prefix)s/lib':  '%(libdir)s',
	}
	self.invariantsubtrees = self.dirmap.keys()
	policy.Policy.__init__(self, *args, **keywords)

    def test(self):
	if self.macros['lib'] == 'lib':
	    # no need to do anything
	    return False
	for d in self.invariantsubtrees:
	    self.dirmap[d %self.macros] = self.dirmap[d] %self.macros
	return True

    def doFile(self, path):
	basename = os.path.basename(path)
	target = util.joinPaths(self.dirmap[self.currentsubtree], basename)
	if os.path.exists(self.macros['destdir'] + os.sep + target):
	    raise DestdirPolicyError(
		"Conflicting library files %s and %s installed" %(
		    path, target))
	util.mkdirChain(self.macros['destdir'] + os.sep +
			self.macros['initdir'])
	util.rename(self.macros['destdir'] + path,
	              self.macros['destdir'] + target)

class RemoveBackupFiles(policy.Policy):
    """
    Kill editor and patch backup files
    """
    invariantinclusions = [
	'~$',
	'\.orig$',
    ]

    def doFile(self, path):
	util.remove(self.macros['destdir']+path)

class Strip(policy.Policy):
    """
    strip executables
    XXX system policy on whether to create debuginfo packages
    """
    invariantinclusions = [
	'%(bindir)s/',
	'%(essentialbindir)s/',
	'%(sbindir)s/',
	'%(essentialsbindir)s/',
	'%(libdir)s/',
	'%(essentiallibdir)s/',
    ]
    def doFile(self, path):
	if not os.path.islink(path):
	    p = self.macros['destdir']+path
	    # XXX do magic internally instead
	    f = os.popen('file '+p, 'r')
	    filetext = f.read()
	    f.close()
	    if (filetext.find('current ar archive') != -1) or \
	       (filetext.find('ELF') != -1 and
	        filetext.find('not stripped') != -1):
		util.execute('%(strip)s -g ' %self.macros +p)

class NormalizeGzip(policy.Policy):
    """
    re-gzip .gz files with -9 -n to get maximum compression and
    avoid meaningless changes overpopulating the database.
    Ignore man/info pages, we'll get them separately while fixing
    up other things
    """
    invariantexceptions = [
	'%(mandir)s/man.*/',
	'%(infodir)s/',
    ]
    invariantinclusions = [
	'.*\.gz'
    ]
    def doFile(self, path):
	# XXX read in header and check whether needed
	# if (byte[3] & 0xC) == 0x8 or byte[8] != 2: recompress
	util.execute('gunzip %s/%s' %(self.macros['destdir'], path));
	util.execute('gzip -n -9 %s/%s' %(self.macros['destdir'], path[:-3]))

class NormalizeBzip(policy.Policy):
    """
    re-bzip .bz2 files with -9  to get maximum compression.
    Ignore man/info pages, we'll get them separately while fixing
    up other things
    """
    invariantexceptions = [
	'%(mandir)s/man.*/',
	'%(infodir)s/',
    ]
    invariantinclusions = [
	'.*\.bz2'
    ]
    def doFile(self, path):
	# XXX read in header and check whether needed
	# if byte[3] != 9: recompress
	util.execute('bunzip2 %s/%s' %(self.macros['destdir'], path));
	util.execute('bzip2 -9 %s/%s' %(self.macros['destdir'], path[:-3]))

class NormalizeManPages(policy.Policy):
    """
    Make all man pages follow sane system policy
     - Fix all man pages' contents:
       - remove '/?%(destdir)s' from all man pages
       - '.so foo.n' becomes a symlink to foo.n
     - (re)compress all man pages with gzip -n -9
     - change all symlinks to point to .gz (if they don't already)
    Exceptions to this policy are ill-defined and thus are not
    currently honored.  Any suggestion that this policy should
    honor inclusion/exception need to include statements of
    precise semantics in that case...
    """
    def _uncompress(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if name.endswith('.gz') and not os.path.islink(path):
		util.execute('gunzip ' + dirname + os.sep + name)

    def _compress(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if not os.path.isdir(path) and not os.path.islink(path) \
	       and not name.endswith('.gz'):
		util.execute('gzip -n -9 ' + dirname + os.sep + name)

    def _dedestdir(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if not os.path.isdir(path) and not os.path.islink(path) \
	       and not name.endswith('.gz'):
		util.execute("sed -i 's,/?%s,,g' %s"
			     %(self.macros['destdir'][1:], path))

    def _sosymlink(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if os.path.exists(path) and not os.path.isdir(path) \
	       and not os.path.islink(path) \
	       and not name.endswith('.gz'):
		# if only .so, change to symlink
		f = file(path)
		lines = f.readlines(512) # we really don't need the whole file
		f.close()

		# delete comment lines first
		newlines = []
		for line in lines:
		    if not self.commentexp.search(line[:-1]):
			newlines.append(line)
		lines = newlines

		# now see if we have only a .so line to replace
		if len(lines) == 1:
		    match = self.soexp.search(lines[0][:-1]) # chop-chop
		    if match:
			# .so is relative to %(mandir)s, so add ../
			log.debug('replacing %s (%s) with symlink ../%s',
                                  name, match.group(0), match.group(1))
			os.remove(path)
			os.symlink(util.normpath('../'+match.group(1)), path)

    def _gzsymlink(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if os.path.islink(path):
		# change symlinks to .gz -> .gz
		contents = os.readlink(path)
		os.remove(path)
		if not contents.endswith('.gz'):
		    contents = contents + '.gz'
		if not path.endswith('.gz'):
		    path = path + '.gz'
		os.symlink(util.normpath(contents), path)

    def __init__(self, *args, **keywords):
	policy.Policy.__init__(self, *args, **keywords)
	self.soexp = re.compile('^\.so (.*\...*)$')
	self.commentexp = re.compile('^\.\\\\"')

    def do(self):
	manpath = self.macros['destdir'] + self.macros['mandir']
	# uncompress all man pages
	os.path.walk(manpath, NormalizeManPages._uncompress, self)
	# remove '/?%(destdir)s'
	os.path.walk(manpath, NormalizeManPages._dedestdir, self)
	# .so foo.n becomes a symlink to foo.n
	os.path.walk(manpath, NormalizeManPages._sosymlink, self)
	# recompress all man pages
	os.path.walk(manpath, NormalizeManPages._compress, self)
	# change all symlinks to point to .gz (if they don't already)
	os.path.walk(manpath, NormalizeManPages._gzsymlink, self)

class NormalizeInfoPages(policy.Policy):
    """
    compress info files and remove dir file
    """
    def do(self):
	dir = self.macros['infodir']+'/dir'
	fsdir = self.macros['destdir']+dir
	if os.path.exists(fsdir):
	    if not policy.policyException(self, dir):
		util.remove(fsdir)
	if os.path.isdir('%(destdir)s/%(infodir)s' %self.macros):
	    infofiles = os.listdir('%(destdir)s/%(infodir)s' %self.macros)
	    for file in infofiles:
		syspath = '%(destdir)s/%(infodir)s/' %self.macros + file
		path = '%(infodir)s/' %self.macros + file
		if not policy.policyException(self, path):
		    if file.endswith('.gz'):
			util.execute('gunzip %s' %syspath)
			syspath = syspath[:-3]
		    util.execute('gzip -n -9 %s' %syspath)

class NormalizeInitscripts(policy.Policy):
    """
    Move all initscripts from /etc/rc.d/init.d/ to their official location
    (if, as is true for the default settings, /etc/rc.d/init.d isn't their
    official location, that is).
    """
    invariantinclusions = [ '/etc/rc.d/init.d/' ]

    def test(self):
	return self.macros['initdir'] != '/etc/rc.d/init.d'

    def doFile(self, path):
	basename = os.path.basename(path)
	target = util.joinPaths(self.macros['initdir'], basename)
	if os.path.exists(self.macros['destdir'] + os.sep + target):
	    raise DestdirPolicyError(
		"Conflicting initscripts %s and %s installed" %(
		    path, target))
	util.mkdirChain(self.macros['destdir'] + os.sep +
			self.macros['initdir'])
	util.rename(self.macros['destdir'] + path,
	            self.macros['destdir'] + target)

class RelativeSymlinks(policy.Policy):
    """
    Make all symlinks relative
    """
    def doFile(self, path):
	fullpath = self.macros['destdir']+path
	if os.path.islink(fullpath):
	    contents = os.readlink(fullpath)
	    if contents.startswith('/'):
		os.remove(fullpath)
		dots = "../"
		dots *= path.count('/') - 1
		normpath = util.normpath(dots + contents)
		# FIXME: make shortest possible relative symlink
		log.debug('Changing absolute symlink %s to relative symlink %s',
                          path, normpath)
		os.symlink(normpath, fullpath)

def DefaultPolicy():
    """
    Return a list of actions that expresses the default policy.
    A recipe can then modify this list if necessary.
    """
    return [
	SanitizeSonames(),
	RemoveExtraLibs(),
	FixupMultilibPaths(),
	RemoveBackupFiles(),
	Strip(),
	NormalizeGzip(),
	NormalizeBzip(),
	NormalizeManPages(),
	NormalizeInfoPages(),
	NormalizeInitscripts(),
	RelativeSymlinks(),
    ]

class DestdirPolicyError(policy.PolicyError):
    pass
<commit_msg>warn people, this should really be fixed in the package<commit_after>#
# Copyright (c) 2004 Specifix, Inc.
# All rights reserved
#

import util
import re
import os
import policy
import log

"""
Module used by recipes to modify the state of the installed %(destdir)s
Classes from this module are not used directly; instead, they are used
through eponymous interfaces in recipe.
"""

class SanitizeSonames(policy.Policy):
    """
    make sure that .so -> SONAME -> fullname
    """
    def do(self):
	pass

class RemoveExtraLibs(policy.Policy):
    """
    Kill .la files and any other similar garbage
    """
    invariantinclusions = [
	'\.la$',
	'%(libdir)s/python.*/site-packages/.*.a$'
    ]

    def doFile(self, path):
	util.remove(self.macros['destdir']+path)

class FixupMultilibPaths(policy.Policy):
    """
    Fix up (and warn) when programs do not know about %(lib) and they
    are supposed to be installing to lib64
    FIXME: must test when we have a multilib platform!
    """
    invariantinclusions = [
	'*.\.(so.*|a)$',
    ]

    def __init__(self, *args, **keywords):
	self.dirmap = {
	    '/lib':            '/%(lib)s',
	    '%(prefix)s/lib':  '%(libdir)s',
	}
	self.invariantsubtrees = self.dirmap.keys()
	policy.Policy.__init__(self, *args, **keywords)

    def test(self):
	if self.macros['lib'] == 'lib':
	    # no need to do anything
	    return False
	for d in self.invariantsubtrees:
	    self.dirmap[d %self.macros] = self.dirmap[d] %self.macros
	return True

    def doFile(self, path):
	basename = os.path.basename(path)
	target = util.joinPaths(self.dirmap[self.currentsubtree], basename)
	if os.path.exists(self.macros['destdir'] + os.sep + target):
	    raise DestdirPolicyError(
		"Conflicting library files %s and %s installed" %(
		    path, target))
	log.warning('Multilib error: file %s found in wrong directory,'
		    'attempting to fix...' %path)
	util.mkdirChain(self.macros['destdir'] + os.sep +
			self.macros['initdir'])
	util.rename(self.macros['destdir'] + path,
	              self.macros['destdir'] + target)

class RemoveBackupFiles(policy.Policy):
    """
    Kill editor and patch backup files
    """
    invariantinclusions = [
	'~$',
	'\.orig$',
    ]

    def doFile(self, path):
	util.remove(self.macros['destdir']+path)

class Strip(policy.Policy):
    """
    strip executables
    XXX system policy on whether to create debuginfo packages
    """
    invariantinclusions = [
	'%(bindir)s/',
	'%(essentialbindir)s/',
	'%(sbindir)s/',
	'%(essentialsbindir)s/',
	'%(libdir)s/',
	'%(essentiallibdir)s/',
    ]
    def doFile(self, path):
	if not os.path.islink(path):
	    p = self.macros['destdir']+path
	    # XXX do magic internally instead
	    f = os.popen('file '+p, 'r')
	    filetext = f.read()
	    f.close()
	    if (filetext.find('current ar archive') != -1) or \
	       (filetext.find('ELF') != -1 and
	        filetext.find('not stripped') != -1):
		util.execute('%(strip)s -g ' %self.macros +p)

class NormalizeGzip(policy.Policy):
    """
    re-gzip .gz files with -9 -n to get maximum compression and
    avoid meaningless changes overpopulating the database.
    Ignore man/info pages, we'll get them separately while fixing
    up other things
    """
    invariantexceptions = [
	'%(mandir)s/man.*/',
	'%(infodir)s/',
    ]
    invariantinclusions = [
	'.*\.gz'
    ]
    def doFile(self, path):
	# XXX read in header and check whether needed
	# if (byte[3] & 0xC) == 0x8 or byte[8] != 2: recompress
	util.execute('gunzip %s/%s' %(self.macros['destdir'], path));
	util.execute('gzip -n -9 %s/%s' %(self.macros['destdir'], path[:-3]))

class NormalizeBzip(policy.Policy):
    """
    re-bzip .bz2 files with -9  to get maximum compression.
    Ignore man/info pages, we'll get them separately while fixing
    up other things
    """
    invariantexceptions = [
	'%(mandir)s/man.*/',
	'%(infodir)s/',
    ]
    invariantinclusions = [
	'.*\.bz2'
    ]
    def doFile(self, path):
	# XXX read in header and check whether needed
	# if byte[3] != 9: recompress
	util.execute('bunzip2 %s/%s' %(self.macros['destdir'], path));
	util.execute('bzip2 -9 %s/%s' %(self.macros['destdir'], path[:-3]))

class NormalizeManPages(policy.Policy):
    """
    Make all man pages follow sane system policy
     - Fix all man pages' contents:
       - remove '/?%(destdir)s' from all man pages
       - '.so foo.n' becomes a symlink to foo.n
     - (re)compress all man pages with gzip -n -9
     - change all symlinks to point to .gz (if they don't already)
    Exceptions to this policy are ill-defined and thus are not
    currently honored.  Any suggestion that this policy should
    honor inclusion/exception need to include statements of
    precise semantics in that case...
    """
    def _uncompress(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if name.endswith('.gz') and not os.path.islink(path):
		util.execute('gunzip ' + dirname + os.sep + name)

    def _compress(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if not os.path.isdir(path) and not os.path.islink(path) \
	       and not name.endswith('.gz'):
		util.execute('gzip -n -9 ' + dirname + os.sep + name)

    def _dedestdir(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if not os.path.isdir(path) and not os.path.islink(path) \
	       and not name.endswith('.gz'):
		util.execute("sed -i 's,/?%s,,g' %s"
			     %(self.macros['destdir'][1:], path))

    def _sosymlink(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if os.path.exists(path) and not os.path.isdir(path) \
	       and not os.path.islink(path) \
	       and not name.endswith('.gz'):
		# if only .so, change to symlink
		f = file(path)
		lines = f.readlines(512) # we really don't need the whole file
		f.close()

		# delete comment lines first
		newlines = []
		for line in lines:
		    if not self.commentexp.search(line[:-1]):
			newlines.append(line)
		lines = newlines

		# now see if we have only a .so line to replace
		if len(lines) == 1:
		    match = self.soexp.search(lines[0][:-1]) # chop-chop
		    if match:
			# .so is relative to %(mandir)s, so add ../
			log.debug('replacing %s (%s) with symlink ../%s',
                                  name, match.group(0), match.group(1))
			os.remove(path)
			os.symlink(util.normpath('../'+match.group(1)), path)

    def _gzsymlink(self, dirname, names):
	for name in names:
	    path = dirname + os.sep + name
	    if os.path.islink(path):
		# change symlinks to .gz -> .gz
		contents = os.readlink(path)
		os.remove(path)
		if not contents.endswith('.gz'):
		    contents = contents + '.gz'
		if not path.endswith('.gz'):
		    path = path + '.gz'
		os.symlink(util.normpath(contents), path)

    def __init__(self, *args, **keywords):
	policy.Policy.__init__(self, *args, **keywords)
	self.soexp = re.compile('^\.so (.*\...*)$')
	self.commentexp = re.compile('^\.\\\\"')

    def do(self):
	manpath = self.macros['destdir'] + self.macros['mandir']
	# uncompress all man pages
	os.path.walk(manpath, NormalizeManPages._uncompress, self)
	# remove '/?%(destdir)s'
	os.path.walk(manpath, NormalizeManPages._dedestdir, self)
	# .so foo.n becomes a symlink to foo.n
	os.path.walk(manpath, NormalizeManPages._sosymlink, self)
	# recompress all man pages
	os.path.walk(manpath, NormalizeManPages._compress, self)
	# change all symlinks to point to .gz (if they don't already)
	os.path.walk(manpath, NormalizeManPages._gzsymlink, self)

class NormalizeInfoPages(policy.Policy):
    """
    compress info files and remove dir file
    """
    def do(self):
	dir = self.macros['infodir']+'/dir'
	fsdir = self.macros['destdir']+dir
	if os.path.exists(fsdir):
	    if not policy.policyException(self, dir):
		util.remove(fsdir)
	if os.path.isdir('%(destdir)s/%(infodir)s' %self.macros):
	    infofiles = os.listdir('%(destdir)s/%(infodir)s' %self.macros)
	    for file in infofiles:
		syspath = '%(destdir)s/%(infodir)s/' %self.macros + file
		path = '%(infodir)s/' %self.macros + file
		if not policy.policyException(self, path):
		    if file.endswith('.gz'):
			util.execute('gunzip %s' %syspath)
			syspath = syspath[:-3]
		    util.execute('gzip -n -9 %s' %syspath)

class NormalizeInitscripts(policy.Policy):
    """
    Move all initscripts from /etc/rc.d/init.d/ to their official location
    (if, as is true for the default settings, /etc/rc.d/init.d isn't their
    official location, that is).
    """
    invariantinclusions = [ '/etc/rc.d/init.d/' ]

    def test(self):
	return self.macros['initdir'] != '/etc/rc.d/init.d'

    def doFile(self, path):
	basename = os.path.basename(path)
	target = util.joinPaths(self.macros['initdir'], basename)
	if os.path.exists(self.macros['destdir'] + os.sep + target):
	    raise DestdirPolicyError(
		"Conflicting initscripts %s and %s installed" %(
		    path, target))
	util.mkdirChain(self.macros['destdir'] + os.sep +
			self.macros['initdir'])
	util.rename(self.macros['destdir'] + path,
	            self.macros['destdir'] + target)

class RelativeSymlinks(policy.Policy):
    """
    Make all symlinks relative
    """
    def doFile(self, path):
	fullpath = self.macros['destdir']+path
	if os.path.islink(fullpath):
	    contents = os.readlink(fullpath)
	    if contents.startswith('/'):
		os.remove(fullpath)
		dots = "../"
		dots *= path.count('/') - 1
		normpath = util.normpath(dots + contents)
		# FIXME: make shortest possible relative symlink
		log.debug('Changing absolute symlink %s to relative symlink %s',
                          path, normpath)
		os.symlink(normpath, fullpath)

def DefaultPolicy():
    """
    Return a list of actions that expresses the default policy.
    A recipe can then modify this list if necessary.
    """
    return [
	SanitizeSonames(),
	RemoveExtraLibs(),
	FixupMultilibPaths(),
	RemoveBackupFiles(),
	Strip(),
	NormalizeGzip(),
	NormalizeBzip(),
	NormalizeManPages(),
	NormalizeInfoPages(),
	NormalizeInitscripts(),
	RelativeSymlinks(),
    ]

class DestdirPolicyError(policy.PolicyError):
    pass
<|endoftext|>


<commit_before>import os

BUILD_DIRECTORY = "build/"
HEADER_TEMPLATE_DIRECTORY = "templates"
BACKBONE_TEMPLATE_DIRECTORY = "templates/underscore-templates/"BACKBONE_TEMPLATE_DIRECTORY = "templates/underscore-templates/"

def format_underscore_template(name, content):
	"""
	Format the template as an Underscore.js template.
	"""
	return '<script type="text/template" id="{0}">\n{1}\n</script>'.format(name, content)

<commit_msg>New: Build function<commit_after>import os

BUILD_DIRECTORY = "build/"
HEADER_TEMPLATE_DIRECTORY = "templates"
BACKBONE_TEMPLATE_DIRECTORY = "templates/underscore-templates/"BACKBONE_TEMPLATE_DIRECTORY = "templates/underscore-templates/"

def format_underscore_template(name, content):
	"""
	Format the template as an Underscore.js template.
	"""
	return '<script type="text/template" id="{0}">\n{1}\n</script>'.format(name, content)

def build_underscore_templates():
	# Open the file to build
	file = open(BUILD_DIRECTORY + "index.html", "w+")
	# Get and write it's content
	file.write(assemble_templates)
	file.close()

<|endoftext|>


<commit_before>__version__ = "0.6.13"
<commit_msg>version<commit_after>__version__ = "0.6.14"
<|endoftext|>


<commit_before>__version__ = "3.11.4"
<commit_msg>version 3.11.5<commit_after>__version__ = "3.11.5"
<|endoftext|>


<commit_before>"""These settings rely on various environment variables being set
"""

import os
import sys
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.celery import CeleryIntegration

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = os.environ['SECRET_KEY']
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split()

DEBUG = bool(os.environ.get('DEBUG', False)) or 'test' in sys.argv

SERVER_EMAIL = 'contact@bustimes.org'

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',
    'django.contrib.sites',
    'django.contrib.sitemaps',
    'haystack',
    'busstops',
    'bustimes',
    'vehicles',
    'vosa',
    'multigtfs',
    'pipeline',
    'antispam',
    'email_obfuscator',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'busstops.middleware.real_ip_middleware',
    'busstops.middleware.not_found_redirect_middleware',
]

if DEBUG and 'runserver' in sys.argv:
    INTERNAL_IPS = ['127.0.0.1']
    INSTALLED_APPS.append('debug_toolbar')
    MIDDLEWARE += [
        'debug_toolbar.middleware.DebugToolbarMiddleware',
        'debug_toolbar_force.middleware.ForceDebugToolbarMiddleware',
    ]

ROOT_URLCONF = 'buses.urls'

HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
    } if DEBUG else {
        'ENGINE': 'haystack.backends.elasticsearch2_backend.Elasticsearch2SearchEngine',
        'URL': 'http://127.0.0.1:9200/',
        'INDEX_NAME': 'haystack',
        'INCLUDE_SPELLING': True,
    },
}
HAYSTACK_IDENTIFIER_METHOD = 'buses.utils.get_identifier'

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': os.environ.get('DB_NAME', 'bustimes'),
        'DISABLE_SERVER_SIDE_CURSORS': True,
        'CONN_MAX_AGE': None
    }
}

if os.environ.get('READ_ONLY_DB_HOST'):
    REPLICA_DATABASES = []
    for i, host in enumerate(os.environ['READ_ONLY_DB_HOST'].split()):
        key = f'read-only-{i}'
        DATABASES[key] = DATABASES['default'].copy()
        DATABASES[key]['HOST'] = os.environ.get('READ_ONLY_DB_HOST')
        REPLICA_DATABASES.append(key)
    DATABASE_ROUTERS = ['multidb.PinningReplicaRouter']
    MIDDLEWARE.append('busstops.middleware.admin_db_middleware')

DATA_UPLOAD_MAX_MEMORY_SIZE = None
DATA_UPLOAD_MAX_NUMBER_FIELDS = 2000

CELERY_BROKER_URL = os.environ.get('CELERY_BROKER_URL', 'redis://localhost:6379')

if DEBUG:
    EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'

STATIC_URL = '/static/'
STATIC_ROOT = os.environ.get('STATIC_ROOT', os.path.join(BASE_DIR, '..', 'bustimes-static'))
MEDIA_URL = '/media/'
MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, '..', 'bustimes-media'))

if DEBUG:
    STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
else:
    STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    'pipeline.finders.PipelineFinder',
)
PIPELINE = {
    'COMPILERS': [
        'busstops.compilers.AutoprefixerSASSCompiler',
    ],
    'STYLESHEETS': {
        'main': {
            'source_filenames': (
                'css/style.scss',
            ),
            'output_filename': 'css/style.css',
        },
        'ie': {
            'source_filenames': (
                'css/ie.scss',
            ),
            'output_filename': 'css/ie.css',
        }
    },
    'JAVASCRIPT': {
        'frontpage': {
            'source_filenames': (
                'js/frontpage.js',
            ),
            'output_filename': 'js/frontpage.min.js',
            'extra_context': {
                'async': True
            }
        },
        'global': {
            'source_filenames': (
                'js/global.js',
            ),
            'output_filename': 'js/global.min.js',
            'extra_context': {
                'async': True
            }
        },
        'timetable': {
            'source_filenames': (
                'js/timetable.js',
            ),
            'output_filename': 'js/timetable.min.js',
            'extra_context': {
                'async': True
            }
        },
        'bigmap': {
            'source_filenames': (
                'js/reqwest.min.js',
                'js/bigmap.js',
            ),
            'output_filename': 'js/bigmap.min.js',
            'extra_context': {
                'async': not DEBUG
            }
        },
        'servicemap': {
            'source_filenames': (
                'js/loadjs/loadjs.min.js',
                'js/reqwest.min.js',
                'js/servicemap.js',
            ),
            'output_filename': 'js/servicemap.min.js',
            'extra_context': {
                'async': not DEBUG
            }
        },

    },
    'YUGLIFY_BINARY': os.path.join(BASE_DIR, 'node_modules', '.bin', 'yuglify'),
    'CSS_COMPRESSOR': None,
    'SASS_BINARY': os.path.join(BASE_DIR, 'node_modules', '.bin', 'sass'),
    'SASS_ARGUMENTS': '--style compressed',
}
PIPELINE_AUTOPREFIXER_BINARY = os.path.join(BASE_DIR, 'node_modules', '.bin', 'postcss')

TEMPLATE_MINIFER_STRIP_FUNCTION = 'buses.utils.minify'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'OPTIONS': {
            'debug': DEBUG,
            'loaders': (
                'django.template.loaders.app_directories.Loader' if DEBUG else (
                    'django.template.loaders.cached.Loader', (
                        'template_minifier.template.loaders.app_directories.Loader',
                    )
                ),
            ),
            'context_processors': (
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'busstops.context_processors.amp',
            )
        }
    }
]

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
    } if 'test' in sys.argv else {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': os.environ.get('MEMCACHED_LOCATION', '127.0.0.1:11211')
    }
}

TIME_FORMAT = 'H:i'
DATE_FORMAT = 'l j F Y'
DATETIME_FORMAT = 'j M H:i'
TIME_ZONE = 'Europe/London'
USE_TZ = True
USE_I18N = False

if not DEBUG and 'test' not in sys.argv and 'collectstatic' not in sys.argv:
    sentry_sdk.init(
        dsn=os.environ.get('SENTRY_DSN'),
        integrations=[DjangoIntegration(), RedisIntegration(), CeleryIntegration()]
    )

    INSTALLED_APPS.append('ddtrace.contrib.django')
    DATADOG_TRACE = {
        'DEFAULT_SERVICE': 'bustimes',
        'TAGS': {'env': 'production'},
    }

TRANSPORTAPI = {
   'app_id': os.environ.get('TRANSPORTAPI_APP_ID'),
   'app_key': os.environ.get('TRANSPORTAPI_APP_KEY')
}
TFL = {
    'app_id': os.environ.get('TFL_APP_ID'),
    'app_key': os.environ.get('TFL_APP_KEY')
}
TFWM = {
    'app_id': os.environ.get('TFWM_APP_ID'),
    'app_key': os.environ.get('TFWM_APP_KEY')
}
THEBUS_KEY = os.environ.get('THEBUS_KEY')

DATA_DIR = os.path.join(BASE_DIR, 'data')
TNDS_DIR = os.path.join(DATA_DIR, 'TNDS')

AKISMET_API_KEY = os.environ.get('AKISMET_API_KEY')
AKISMET_SITE_URL = 'https://bustimes.org'

IE_COLLECTIONS = (
    'goahead', 'luasbus', 'dublinbus', 'kenneallys', 'locallink', 'irishrail', 'ferries',
    'manda', 'finnegans', 'citylink', 'nitelink', 'buseireann', 'mcgeehan',
    'mkilbride', 'expressbus', 'edmoore', 'collins', 'luas', 'sro',
    'dublincoach', 'burkes', 'mhealy', 'kearns', 'josfoley', 'buggy',
    'jjkavanagh', 'citydirect', 'aircoach', 'matthews', 'wexfordbus',
    'dualway', 'tralee', 'sbloom', 'mcginley', 'swordsexpress', 'suirway',
    'sdoherty', 'pjmartley', 'mortons', 'mgray', 'mcgrath', 'mangan',
    'lallycoach', 'halpenny', 'eurobus', 'donnellys', 'cmadigan', 'bkavanagh',
    'ptkkenneally', 'farragher', 'fedateoranta', 'ashbourneconnect'
)

SITE_ID = 1
<commit_msg>fix read only db host<commit_after>"""These settings rely on various environment variables being set
"""

import os
import sys
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.celery import CeleryIntegration

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = os.environ['SECRET_KEY']
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split()

DEBUG = bool(os.environ.get('DEBUG', False)) or 'test' in sys.argv

SERVER_EMAIL = 'contact@bustimes.org'

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',
    'django.contrib.sites',
    'django.contrib.sitemaps',
    'haystack',
    'busstops',
    'bustimes',
    'vehicles',
    'vosa',
    'multigtfs',
    'pipeline',
    'antispam',
    'email_obfuscator',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'busstops.middleware.real_ip_middleware',
    'busstops.middleware.not_found_redirect_middleware',
]

if DEBUG and 'runserver' in sys.argv:
    INTERNAL_IPS = ['127.0.0.1']
    INSTALLED_APPS.append('debug_toolbar')
    MIDDLEWARE += [
        'debug_toolbar.middleware.DebugToolbarMiddleware',
        'debug_toolbar_force.middleware.ForceDebugToolbarMiddleware',
    ]

ROOT_URLCONF = 'buses.urls'

HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
    } if DEBUG else {
        'ENGINE': 'haystack.backends.elasticsearch2_backend.Elasticsearch2SearchEngine',
        'URL': 'http://127.0.0.1:9200/',
        'INDEX_NAME': 'haystack',
        'INCLUDE_SPELLING': True,
    },
}
HAYSTACK_IDENTIFIER_METHOD = 'buses.utils.get_identifier'

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': os.environ.get('DB_NAME', 'bustimes'),
        'DISABLE_SERVER_SIDE_CURSORS': True,
        'CONN_MAX_AGE': None
    }
}

if os.environ.get('READ_ONLY_DB_HOST'):
    REPLICA_DATABASES = []
    for i, host in enumerate(os.environ['READ_ONLY_DB_HOST'].split()):
        key = f'read-only-{i}'
        DATABASES[key] = DATABASES['default'].copy()
        DATABASES[key]['HOST'] = host
        REPLICA_DATABASES.append(key)
    DATABASE_ROUTERS = ['multidb.PinningReplicaRouter']
    MIDDLEWARE.append('busstops.middleware.admin_db_middleware')

DATA_UPLOAD_MAX_MEMORY_SIZE = None
DATA_UPLOAD_MAX_NUMBER_FIELDS = 2000

CELERY_BROKER_URL = os.environ.get('CELERY_BROKER_URL', 'redis://localhost:6379')

if DEBUG:
    EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'

STATIC_URL = '/static/'
STATIC_ROOT = os.environ.get('STATIC_ROOT', os.path.join(BASE_DIR, '..', 'bustimes-static'))
MEDIA_URL = '/media/'
MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, '..', 'bustimes-media'))

if DEBUG:
    STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
else:
    STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    'pipeline.finders.PipelineFinder',
)
PIPELINE = {
    'COMPILERS': [
        'busstops.compilers.AutoprefixerSASSCompiler',
    ],
    'STYLESHEETS': {
        'main': {
            'source_filenames': (
                'css/style.scss',
            ),
            'output_filename': 'css/style.css',
        },
        'ie': {
            'source_filenames': (
                'css/ie.scss',
            ),
            'output_filename': 'css/ie.css',
        }
    },
    'JAVASCRIPT': {
        'frontpage': {
            'source_filenames': (
                'js/frontpage.js',
            ),
            'output_filename': 'js/frontpage.min.js',
            'extra_context': {
                'async': True
            }
        },
        'global': {
            'source_filenames': (
                'js/global.js',
            ),
            'output_filename': 'js/global.min.js',
            'extra_context': {
                'async': True
            }
        },
        'timetable': {
            'source_filenames': (
                'js/timetable.js',
            ),
            'output_filename': 'js/timetable.min.js',
            'extra_context': {
                'async': True
            }
        },
        'bigmap': {
            'source_filenames': (
                'js/reqwest.min.js',
                'js/bigmap.js',
            ),
            'output_filename': 'js/bigmap.min.js',
            'extra_context': {
                'async': not DEBUG
            }
        },
        'servicemap': {
            'source_filenames': (
                'js/loadjs/loadjs.min.js',
                'js/reqwest.min.js',
                'js/servicemap.js',
            ),
            'output_filename': 'js/servicemap.min.js',
            'extra_context': {
                'async': not DEBUG
            }
        },

    },
    'YUGLIFY_BINARY': os.path.join(BASE_DIR, 'node_modules', '.bin', 'yuglify'),
    'CSS_COMPRESSOR': None,
    'SASS_BINARY': os.path.join(BASE_DIR, 'node_modules', '.bin', 'sass'),
    'SASS_ARGUMENTS': '--style compressed',
}
PIPELINE_AUTOPREFIXER_BINARY = os.path.join(BASE_DIR, 'node_modules', '.bin', 'postcss')

TEMPLATE_MINIFER_STRIP_FUNCTION = 'buses.utils.minify'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'OPTIONS': {
            'debug': DEBUG,
            'loaders': (
                'django.template.loaders.app_directories.Loader' if DEBUG else (
                    'django.template.loaders.cached.Loader', (
                        'template_minifier.template.loaders.app_directories.Loader',
                    )
                ),
            ),
            'context_processors': (
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'busstops.context_processors.amp',
            )
        }
    }
]

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
    } if 'test' in sys.argv else {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': os.environ.get('MEMCACHED_LOCATION', '127.0.0.1:11211')
    }
}

TIME_FORMAT = 'H:i'
DATE_FORMAT = 'l j F Y'
DATETIME_FORMAT = 'j M H:i'
TIME_ZONE = 'Europe/London'
USE_TZ = True
USE_I18N = False

if not DEBUG and 'test' not in sys.argv and 'collectstatic' not in sys.argv:
    sentry_sdk.init(
        dsn=os.environ.get('SENTRY_DSN'),
        integrations=[DjangoIntegration(), RedisIntegration(), CeleryIntegration()]
    )

    INSTALLED_APPS.append('ddtrace.contrib.django')
    DATADOG_TRACE = {
        'DEFAULT_SERVICE': 'bustimes',
        'TAGS': {'env': 'production'},
    }

TRANSPORTAPI = {
   'app_id': os.environ.get('TRANSPORTAPI_APP_ID'),
   'app_key': os.environ.get('TRANSPORTAPI_APP_KEY')
}
TFL = {
    'app_id': os.environ.get('TFL_APP_ID'),
    'app_key': os.environ.get('TFL_APP_KEY')
}
TFWM = {
    'app_id': os.environ.get('TFWM_APP_ID'),
    'app_key': os.environ.get('TFWM_APP_KEY')
}
THEBUS_KEY = os.environ.get('THEBUS_KEY')

DATA_DIR = os.path.join(BASE_DIR, 'data')
TNDS_DIR = os.path.join(DATA_DIR, 'TNDS')

AKISMET_API_KEY = os.environ.get('AKISMET_API_KEY')
AKISMET_SITE_URL = 'https://bustimes.org'

IE_COLLECTIONS = (
    'goahead', 'luasbus', 'dublinbus', 'kenneallys', 'locallink', 'irishrail', 'ferries',
    'manda', 'finnegans', 'citylink', 'nitelink', 'buseireann', 'mcgeehan',
    'mkilbride', 'expressbus', 'edmoore', 'collins', 'luas', 'sro',
    'dublincoach', 'burkes', 'mhealy', 'kearns', 'josfoley', 'buggy',
    'jjkavanagh', 'citydirect', 'aircoach', 'matthews', 'wexfordbus',
    'dualway', 'tralee', 'sbloom', 'mcginley', 'swordsexpress', 'suirway',
    'sdoherty', 'pjmartley', 'mortons', 'mgray', 'mcgrath', 'mangan',
    'lallycoach', 'halpenny', 'eurobus', 'donnellys', 'cmadigan', 'bkavanagh',
    'ptkkenneally', 'farragher', 'fedateoranta', 'ashbourneconnect'
)

SITE_ID = 1
<|endoftext|>


<commit_before>from django import forms
from django.contrib import admin
from django.contrib.gis.db.models import PointField
from django.contrib.gis.forms import OSMWidget
from django.contrib.postgres.aggregates import StringAgg
from django.contrib.postgres.search import SearchQuery, SearchRank
from django.db.models import Q, F, Exists, OuterRef, CharField
from django.db.models.functions import Cast
from django.urls import reverse
from django.utils.html import format_html

from sql_util.utils import SubqueryCount

from bustimes.models import Route
from vehicles.models import VehicleJourney
from . import models

@admin.register(models.AdminArea)
class AdminAreaAdmin(admin.ModelAdmin):
    list_display = ('name', 'id', 'atco_code', 'region_id')
    list_filter = ('region_id',)
    search_fields = ('atco_code',)

class StopCodeInline(admin.TabularInline):
    model = models.StopCode
    raw_id_fields = ['source']

@admin.register(models.StopPoint)
class StopPointAdmin(admin.ModelAdmin):
    list_display = ['atco_code', 'naptan_code', 'locality', 'admin_area', '__str__']
    list_select_related = ['locality', 'admin_area']
    list_filter = ['stop_type', 'service__region', 'admin_area']
    raw_id_fields = ['places', 'admin_area']
    search_fields = ['atco_code']
    ordering = ['atco_code']
    formfield_overrides = {
        PointField: {'widget': OSMWidget}
    }
    inlines = [StopCodeInline]
    show_full_result_count = False

    def get_search_results(self, request, queryset, search_term):
        if not search_term:
            return super().get_search_results(request, queryset, search_term)

        query = SearchQuery(search_term, search_type="websearch", config="english")
        rank = SearchRank(F('locality__search_vector'), query)
        query = Q(locality__search_vector=query)
        if ' ' not in search_term:
            query |= Q(atco_code=search_term)
        queryset = queryset.annotate(rank=rank).filter(query).order_by("-rank")
        return queryset, False

@admin.register(models.StopCode)
class StopCodeAdmin(admin.ModelAdmin):
    list_display = ['stop', 'code', 'source']
    raw_id_fields = ['stop', 'source']

class OperatorCodeInline(admin.TabularInline):
    model = models.OperatorCode

class OperatorAdminForm(forms.ModelForm):
    class Meta:
        widgets = {
            'address': forms.Textarea,
            'twitter': forms.Textarea,
        }

@admin.register(models.Operator)
class OperatorAdmin(admin.ModelAdmin):
    form = OperatorAdminForm
    list_display = ['name', 'operator_codes', 'id', 'vehicle_mode', 'parent', 'region_id',
                    'services', 'vehicles', 'twitter']
    list_filter = ('region', 'vehicle_mode', 'payment_methods', 'parent')
    search_fields = ('id', 'name')
    raw_id_fields = ('region', 'regions', 'siblings', 'colour')
    inlines = [OperatorCodeInline]
    readonly_fields = ['search_vector']
    prepopulated_fields = {"slug": ("name",)}
    autocomplete_fields = ('licences',)

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(
                services=SubqueryCount('service', filter=Q(service__current=True)),
                vehicles=SubqueryCount('vehicle')
            ).prefetch_related('operatorcode_set')
        return queryset

    @admin.display(ordering='services')
    def services(self, obj):
        url = reverse('admin:busstops_service_changelist')
        return format_html('{}', url, obj.id, obj.services)

    @admin.display(ordering='vehicles')
    def vehicles(self, obj):
        url = reverse('admin:vehicles_vehicle_changelist')
        return format_html('{}', url, obj.id, obj.vehicles)

    def get_search_results(self, request, queryset, search_term):
        queryset, use_distinct = super().get_search_results(request, queryset, search_term)

        if request.path.endswith('/autocomplete/'):
            queryset = queryset.filter(Exists(models.Service.objects.filter(operator=OuterRef('pk'), current=True)))

        return queryset, use_distinct

    @staticmethod
    def payment(obj):
        return ', '.join(str(code) for code in obj.payment_methods.all())

    @staticmethod
    def operator_codes(obj):
        return ', '.join(str(code) for code in obj.operatorcode_set.all())

class ServiceCodeInline(admin.TabularInline):
    model = models.ServiceCode

class RouteInline(admin.TabularInline):
    model = Route
    show_change_link = True
    fields = ['source', 'code', 'service_code']
    raw_id_fields = ['source']

class FromServiceLinkInline(admin.TabularInline):
    model = models.ServiceLink
    fk_name = 'from_service'
    autocomplete_fields = ['to_service']

class ToServiceLinkInline(FromServiceLinkInline):
    fk_name = 'to_service'
    autocomplete_fields = ['from_service']

@admin.register(models.Service)
class ServiceAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'service_code', 'mode', 'region_id', 'routes',
                    'current', 'timetable_wrong', 'colour', 'line_brand')
    list_filter = ('current', 'timetable_wrong', 'mode', 'region',
                   ('source', admin.RelatedOnlyFieldListFilter),
                   ('operator', admin.RelatedOnlyFieldListFilter))
    search_fields = ('service_code', 'line_name', 'line_brand', 'description')
    raw_id_fields = ('operator', 'stops', 'colour', 'source')
    inlines = [ServiceCodeInline, RouteInline, FromServiceLinkInline, ToServiceLinkInline]
    readonly_fields = ['search_vector']
    list_editable = ['colour', 'line_brand']
    list_select_related = ['colour']

    def routes(self, obj):
        return obj.routes
    routes.admin_order_field = 'routes'

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            queryset = queryset.annotate(routes=SubqueryCount('route'))
        return queryset

    def get_search_results(self, request, queryset, search_term):
        if search_term and request.path.endswith('/autocomplete/'):
            queryset = queryset.filter(current=True)

            query = SearchQuery(search_term, search_type="websearch", config="english")
            rank = SearchRank(F('search_vector'), query)
            queryset = (
                queryset.annotate(rank=rank)
                .filter(Q(search_vector=query) | Q(service_code=search_term))
                .order_by("-rank")
            )
            return queryset, False

        return super().get_search_results(request, queryset, search_term)

@admin.register(models.ServiceLink)
class ServiceLinkAdmin(admin.ModelAdmin):
    save_as = True
    list_display = ('from_service', 'from_service__current', 'to_service', 'to_service__current', 'how')
    list_filter = ('from_service__current', 'to_service__current', 'from_service__source', 'to_service__source')
    autocomplete_fields = ('from_service', 'to_service')

    @staticmethod
    def from_service__current(obj):
        return obj.from_service.current

    @staticmethod
    def to_service__current(obj):
        return obj.to_service.current

@admin.register(models.Locality)
class LocalityAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'slug')
    search_fields = ('id', 'name')
    raw_id_fields = ('adjacent',)
    list_filter = ('admin_area', 'admin_area__region')

@admin.register(models.OperatorCode)
class OperatorCodeAdmin(admin.ModelAdmin):
    save_as = True
    list_display = ('id', 'operator', 'source', 'code')
    list_filter = [
        ('source', admin.RelatedOnlyFieldListFilter)
    ]
    search_fields = ('code',)
    raw_id_fields = ('operator',)

@admin.register(models.ServiceCode)
class ServiceCodeAdmin(admin.ModelAdmin):
    list_display = ['id', 'service', 'scheme', 'code']
    list_filter = [
        'scheme',
        'service__current',
        ('service__operator', admin.RelatedOnlyFieldListFilter),
        'service__stops__admin_area'
    ]
    search_fields = ['code', 'service__line_name', 'service__description']
    autocomplete_fields = ['service']

@admin.register(models.ServiceColour)
class ServiceColourAdmin(admin.ModelAdmin):
    list_display = ['preview', 'foreground', 'background', 'services']
    search_fields = ['name']
    list_filter = [
        ('service__operator', admin.EmptyFieldListFilter)
    ]

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            queryset = queryset.annotate(services=SubqueryCount('service', filter=Q(current=True)))
        return queryset

    def services(self, obj):
        return obj.services
    services.admin_order_field = 'services'

@admin.register(models.Place)
class PlaceAdmin(admin.ModelAdmin):
    list_filter = ('source',)
    search_fields = ('name',)

@admin.register(models.DataSource)
class DataSourceAdmin(admin.ModelAdmin):
    search_fields = ('name', 'url')
    list_display = ('name', 'url', 'sha1', 'datetime', 'settings', 'routes', 'services', 'journeys')
    list_filter = (
        ('route', admin.EmptyFieldListFilter),
        ('service', admin.EmptyFieldListFilter),
        ('vehiclejourney', admin.EmptyFieldListFilter)
    )
    actions = ['delete_routes', 'remove_datetimes']
    show_full_result_count = False

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(
                routes=SubqueryCount('route'),
                services=SubqueryCount('service', filter=Q(current=True)),
                journeys=Exists(VehicleJourney.objects.filter(source=OuterRef('id'))),
            ).prefetch_related('operatorcode_set')
        return queryset

    def routes(self, obj):
        url = reverse('admin:bustimes_route_changelist')
        return format_html('{}', url, obj.id, obj.routes)
    routes.admin_order_field = 'routes'

    def services(self, obj):
        url = reverse('admin:busstops_service_changelist')
        return format_html('{}', url, obj.id, obj.services)
    services.admin_order_field = 'services'

    def journeys(self, obj):
        url = reverse('admin:vehicles_vehiclejourney_changelist')
        return format_html('{}', url, obj.id, obj.journeys)
    journeys.admin_order_field = 'journeys'

    def delete_routes(self, request, queryset):
        result = Route.objects.filter(source__in=queryset).delete()
        self.message_user(request, result)

    def remove_datetimes(self, request, queryset):
        result = queryset.order_by().update(datetime=None)
        self.message_user(request, result)

@admin.register(models.SIRISource)
class SIRISourceAdmin(admin.ModelAdmin):
    list_display = ('name', 'url', 'requestor_ref', 'areas', 'get_poorly')

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(
                areas=StringAgg(Cast('admin_areas__atco_code', output_field=CharField()), ', ')
            )
        return queryset

    @staticmethod
    def areas(obj):
        return obj.areas

class PaymentMethodOperatorInline(admin.TabularInline):
    model = models.PaymentMethod.operator_set.through
    autocomplete_fields = ['operator']

@admin.register(models.PaymentMethod)
class PaymentMethodAdmin(admin.ModelAdmin):
    list_display = ('name', 'url', 'operators')
    inlines = [PaymentMethodOperatorInline]

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(operators=StringAgg('operator', ', ', distinct=True))
        return queryset

    @staticmethod
    def operators(obj):
        return obj.operators

admin.site.register(models.Region)
admin.site.register(models.District)
admin.site.register(models.StopArea)
<commit_msg>add id to service admin changelist<commit_after>from django import forms
from django.contrib import admin
from django.contrib.gis.db.models import PointField
from django.contrib.gis.forms import OSMWidget
from django.contrib.postgres.aggregates import StringAgg
from django.contrib.postgres.search import SearchQuery, SearchRank
from django.db.models import Q, F, Exists, OuterRef, CharField
from django.db.models.functions import Cast
from django.urls import reverse
from django.utils.html import format_html

from sql_util.utils import SubqueryCount

from bustimes.models import Route
from vehicles.models import VehicleJourney
from . import models

@admin.register(models.AdminArea)
class AdminAreaAdmin(admin.ModelAdmin):
    list_display = ('name', 'id', 'atco_code', 'region_id')
    list_filter = ('region_id',)
    search_fields = ('atco_code',)

class StopCodeInline(admin.TabularInline):
    model = models.StopCode
    raw_id_fields = ['source']

@admin.register(models.StopPoint)
class StopPointAdmin(admin.ModelAdmin):
    list_display = ['atco_code', 'naptan_code', 'locality', 'admin_area', '__str__']
    list_select_related = ['locality', 'admin_area']
    list_filter = ['stop_type', 'service__region', 'admin_area']
    raw_id_fields = ['places', 'admin_area']
    search_fields = ['atco_code']
    ordering = ['atco_code']
    formfield_overrides = {
        PointField: {'widget': OSMWidget}
    }
    inlines = [StopCodeInline]
    show_full_result_count = False

    def get_search_results(self, request, queryset, search_term):
        if not search_term:
            return super().get_search_results(request, queryset, search_term)

        query = SearchQuery(search_term, search_type="websearch", config="english")
        rank = SearchRank(F('locality__search_vector'), query)
        query = Q(locality__search_vector=query)
        if ' ' not in search_term:
            query |= Q(atco_code=search_term)
        queryset = queryset.annotate(rank=rank).filter(query).order_by("-rank")
        return queryset, False

@admin.register(models.StopCode)
class StopCodeAdmin(admin.ModelAdmin):
    list_display = ['stop', 'code', 'source']
    raw_id_fields = ['stop', 'source']

class OperatorCodeInline(admin.TabularInline):
    model = models.OperatorCode

class OperatorAdminForm(forms.ModelForm):
    class Meta:
        widgets = {
            'address': forms.Textarea,
            'twitter': forms.Textarea,
        }

@admin.register(models.Operator)
class OperatorAdmin(admin.ModelAdmin):
    form = OperatorAdminForm
    list_display = ['name', 'operator_codes', 'id', 'vehicle_mode', 'parent', 'region_id',
                    'services', 'vehicles', 'twitter']
    list_filter = ('region', 'vehicle_mode', 'payment_methods', 'parent')
    search_fields = ('id', 'name')
    raw_id_fields = ('region', 'regions', 'siblings', 'colour')
    inlines = [OperatorCodeInline]
    readonly_fields = ['search_vector']
    prepopulated_fields = {"slug": ("name",)}
    autocomplete_fields = ('licences',)

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(
                services=SubqueryCount('service', filter=Q(service__current=True)),
                vehicles=SubqueryCount('vehicle')
            ).prefetch_related('operatorcode_set')
        return queryset

    @admin.display(ordering='services')
    def services(self, obj):
        url = reverse('admin:busstops_service_changelist')
        return format_html('{}', url, obj.id, obj.services)

    @admin.display(ordering='vehicles')
    def vehicles(self, obj):
        url = reverse('admin:vehicles_vehicle_changelist')
        return format_html('{}', url, obj.id, obj.vehicles)

    def get_search_results(self, request, queryset, search_term):
        queryset, use_distinct = super().get_search_results(request, queryset, search_term)

        if request.path.endswith('/autocomplete/'):
            queryset = queryset.filter(Exists(models.Service.objects.filter(operator=OuterRef('pk'), current=True)))

        return queryset, use_distinct

    @staticmethod
    def payment(obj):
        return ', '.join(str(code) for code in obj.payment_methods.all())

    @staticmethod
    def operator_codes(obj):
        return ', '.join(str(code) for code in obj.operatorcode_set.all())

class ServiceCodeInline(admin.TabularInline):
    model = models.ServiceCode

class RouteInline(admin.TabularInline):
    model = Route
    show_change_link = True
    fields = ['source', 'code', 'service_code']
    raw_id_fields = ['source']

class FromServiceLinkInline(admin.TabularInline):
    model = models.ServiceLink
    fk_name = 'from_service'
    autocomplete_fields = ['to_service']

class ToServiceLinkInline(FromServiceLinkInline):
    fk_name = 'to_service'
    autocomplete_fields = ['from_service']

@admin.register(models.Service)
class ServiceAdmin(admin.ModelAdmin):
    list_display = ('id', '__str__', 'service_code', 'mode', 'region_id', 'routes',
                    'current', 'timetable_wrong', 'colour', 'line_brand')
    list_filter = ('current', 'timetable_wrong', 'mode', 'region',
                   ('source', admin.RelatedOnlyFieldListFilter),
                   ('operator', admin.RelatedOnlyFieldListFilter))
    search_fields = ('service_code', 'line_name', 'line_brand', 'description')
    raw_id_fields = ('operator', 'stops', 'colour', 'source')
    inlines = [ServiceCodeInline, RouteInline, FromServiceLinkInline, ToServiceLinkInline]
    readonly_fields = ['search_vector']
    list_editable = ['colour', 'line_brand']
    list_select_related = ['colour']

    def routes(self, obj):
        return obj.routes
    routes.admin_order_field = 'routes'

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            queryset = queryset.annotate(routes=SubqueryCount('route'))
        return queryset

    def get_search_results(self, request, queryset, search_term):
        if search_term and request.path.endswith('/autocomplete/'):
            queryset = queryset.filter(current=True)

            query = SearchQuery(search_term, search_type="websearch", config="english")
            rank = SearchRank(F('search_vector'), query)
            queryset = (
                queryset.annotate(rank=rank)
                .filter(Q(search_vector=query) | Q(service_code=search_term))
                .order_by("-rank")
            )
            return queryset, False

        return super().get_search_results(request, queryset, search_term)

@admin.register(models.ServiceLink)
class ServiceLinkAdmin(admin.ModelAdmin):
    save_as = True
    list_display = ('from_service', 'from_service__current', 'to_service', 'to_service__current', 'how')
    list_filter = ('from_service__current', 'to_service__current', 'from_service__source', 'to_service__source')
    autocomplete_fields = ('from_service', 'to_service')

    @staticmethod
    def from_service__current(obj):
        return obj.from_service.current

    @staticmethod
    def to_service__current(obj):
        return obj.to_service.current

@admin.register(models.Locality)
class LocalityAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'slug')
    search_fields = ('id', 'name')
    raw_id_fields = ('adjacent',)
    list_filter = ('admin_area', 'admin_area__region')

@admin.register(models.OperatorCode)
class OperatorCodeAdmin(admin.ModelAdmin):
    save_as = True
    list_display = ('id', 'operator', 'source', 'code')
    list_filter = [
        ('source', admin.RelatedOnlyFieldListFilter)
    ]
    search_fields = ('code',)
    raw_id_fields = ('operator',)

@admin.register(models.ServiceCode)
class ServiceCodeAdmin(admin.ModelAdmin):
    list_display = ['id', 'service', 'scheme', 'code']
    list_filter = [
        'scheme',
        'service__current',
        ('service__operator', admin.RelatedOnlyFieldListFilter),
        'service__stops__admin_area'
    ]
    search_fields = ['code', 'service__line_name', 'service__description']
    autocomplete_fields = ['service']

@admin.register(models.ServiceColour)
class ServiceColourAdmin(admin.ModelAdmin):
    list_display = ['preview', 'foreground', 'background', 'services']
    search_fields = ['name']
    list_filter = [
        ('service__operator', admin.EmptyFieldListFilter)
    ]

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            queryset = queryset.annotate(services=SubqueryCount('service', filter=Q(current=True)))
        return queryset

    def services(self, obj):
        return obj.services
    services.admin_order_field = 'services'

@admin.register(models.Place)
class PlaceAdmin(admin.ModelAdmin):
    list_filter = ('source',)
    search_fields = ('name',)

@admin.register(models.DataSource)
class DataSourceAdmin(admin.ModelAdmin):
    search_fields = ('name', 'url')
    list_display = ('name', 'url', 'sha1', 'datetime', 'settings', 'routes', 'services', 'journeys')
    list_filter = (
        ('route', admin.EmptyFieldListFilter),
        ('service', admin.EmptyFieldListFilter),
        ('vehiclejourney', admin.EmptyFieldListFilter)
    )
    actions = ['delete_routes', 'remove_datetimes']
    show_full_result_count = False

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(
                routes=SubqueryCount('route'),
                services=SubqueryCount('service', filter=Q(current=True)),
                journeys=Exists(VehicleJourney.objects.filter(source=OuterRef('id'))),
            ).prefetch_related('operatorcode_set')
        return queryset

    def routes(self, obj):
        url = reverse('admin:bustimes_route_changelist')
        return format_html('{}', url, obj.id, obj.routes)
    routes.admin_order_field = 'routes'

    def services(self, obj):
        url = reverse('admin:busstops_service_changelist')
        return format_html('{}', url, obj.id, obj.services)
    services.admin_order_field = 'services'

    def journeys(self, obj):
        url = reverse('admin:vehicles_vehiclejourney_changelist')
        return format_html('{}', url, obj.id, obj.journeys)
    journeys.admin_order_field = 'journeys'

    def delete_routes(self, request, queryset):
        result = Route.objects.filter(source__in=queryset).delete()
        self.message_user(request, result)

    def remove_datetimes(self, request, queryset):
        result = queryset.order_by().update(datetime=None)
        self.message_user(request, result)

@admin.register(models.SIRISource)
class SIRISourceAdmin(admin.ModelAdmin):
    list_display = ('name', 'url', 'requestor_ref', 'areas', 'get_poorly')

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(
                areas=StringAgg(Cast('admin_areas__atco_code', output_field=CharField()), ', ')
            )
        return queryset

    @staticmethod
    def areas(obj):
        return obj.areas

class PaymentMethodOperatorInline(admin.TabularInline):
    model = models.PaymentMethod.operator_set.through
    autocomplete_fields = ['operator']

@admin.register(models.PaymentMethod)
class PaymentMethodAdmin(admin.ModelAdmin):
    list_display = ('name', 'url', 'operators')
    inlines = [PaymentMethodOperatorInline]

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if 'changelist' in request.resolver_match.view_name:
            return queryset.annotate(operators=StringAgg('operator', ', ', distinct=True))
        return queryset

    @staticmethod
    def operators(obj):
        return obj.operators

admin.site.register(models.Region)
admin.site.register(models.District)
admin.site.register(models.StopArea)
<|endoftext|>


<commit_before>#!/usr/bin/env python
"""inotify: Wrapper around the inotify syscalls providing both a function based and file like interface"""

from utils import get_buffered_length as _get_buffered_length
from cffi import FFI as _FFI
import errno as _errno

_ffi = _FFI()
_ffi.cdef("""
/*
 * struct inotify_event - structure read from the inotify device for each event
 *
 * When you are watching a directory, you will receive the filename for events
 * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd.
 */
struct inotify_event {
        int           wd;
        uint32_t      mask;
        uint32_t      cookie;
        uint32_t      len;
//        char          name[0];
};

/* the following are legal, implemented events that user-space can watch for */
#define IN_ACCESS        ...  /* File was accessed */
#define IN_MODIFY        ...  /* File was modified */
#define IN_ATTRIB        ...  /* Metadata changed */
#define IN_CLOSE_WRITE   ...  /* Writtable file was closed */
#define IN_CLOSE_NOWRITE ...  /* Unwrittable file closed */
#define IN_OPEN          ...  /* File was opened */
#define IN_MOVED_FROM    ...  /* File was moved from X */
#define IN_MOVED_TO      ...  /* File was moved to Y */
#define IN_CREATE        ...  /* Subfile was created */
#define IN_DELETE        ...  /* Subfile was deleted */
#define IN_DELETE_SELF   ...  /* Self was deleted */
#define IN_MOVE_SELF     ...  /* Self was moved */

/* the following are legal events.  they are sent as needed to any watch */
#define IN_UNMOUNT       ...  /* Backing fs was unmounted */
#define IN_Q_OVERFLOW    ...  /* Event queued overflowed */
#define IN_IGNORED       ...  /* File was ignored */

/* helper events */
#define IN_CLOSE         ...  /* close */
#define IN_MOVE          ...  /* moves */

/* special flags */
#define IN_ONLYDIR       ...  /* only watch the path if it is a directory */
#define IN_DONT_FOLLOW   ...  /* don't follow a sym link */
#define IN_EXCL_UNLINK   ...  /* exclude events on unlinked objects */
#define IN_MASK_ADD      ...  /* add to the mask of an already existing watch */
#define IN_ISDIR         ...  /* event occurred against dir */
#define IN_ONESHOT       ...  /* only send event once */

/*
 * All of the events - we build the list by hand so that we can add flags in
 * the future and not break backward compatibility.  Apps will get only the
 * events that they originally wanted.  Be sure to add new events here!
 */
#define IN_ALL_EVENTS  ...

/* Flags for sys_inotify_init1.  */
#define IN_CLOEXEC  ...
#define IN_NONBLOCK ...

int inotify_init(void);
int inotify_init1(int flags);
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
int inotify_rm_watch(int fd, int wd);
""")

_C = _ffi.verify("""
#include <sys/inotify.h>
#include <sys/ioctl.h>
""", libraries=[])

def inotify_init(flags=0):
    """Initialise an inotify instnace and return a File Descriptor to refrence is

    Arguments:
    -----------
    Flags:
    -------
    IN_CLOEXEC: Automatically close the inotify handle on exec()
    IN_NONBLOCK: Place the file descriptor in non blocking mode
    """
    fd = _C.inotify_init1(flags)

    if fd < 0:
        err = _ffi.errno
        if err == _errno.EINVAL:
            raise ValueError("Invalid argument or flag")
        elif err == _errno.EMFILE:
            raise OSError("Maximum inotify instances reached")
        elif err == _errno.ENFILE:
            raise OSError("File descriptor limit hit")
        elif err == _errno.ENOMEM:
            raise MemoryError("Insufficent kernel memory avalible")
        else:
            # If you are here, its a bug. send us the traceback
            raise ValueError("Unknown Error: {}".format(err))

    return fd

def inotify_add_watch(fd, path, mask):
    """Start watching a filepath for events

    Arguments:
    -----------
    fd:    The inotify file descriptor to attach the watch to
    path:  The path to the file/directory to be monitored for events
    mask:  The events to listen for

    Flags:
    -------
    IN_ACCESS:        File was accessed
    IN_MODIFY:        File was modified
    IN_ATTRIB:        Metadata changed
    IN_CLOSE_WRITE:   Writtable file was closed
    IN_CLOSE_NOWRITE: Unwrittable file closed
    IN_OPEN:          File was opened
    IN_MOVED_FROM:    File was moved from X
    IN_MOVED_TO:      File was moved to Y
    IN_CREATE:        Subfile was created
    IN_DELETE:        Subfile was deleted
    IN_DELETE_SELF:   Self was deleted
    IN_MOVE_SELF:     Self was moved

    IN_ONLYDIR:      only watch the path if it is a directory
    IN_DONT_FOLLOW:  don't follow a sym link
    IN_EXCL_UNLINK:  exclude events on unlinked objects
    IN_MASK_ADD:     add to the mask of an already existing watch
    IN_ISDIR:        event occurred against dir
    IN_ONESHOT:      only send event once

    Returns:
    ---------
    int: A watch descriptor that can be passed to inotify_rm_watch

    Exceptions:
    ------------
    ValueError:
    * No valid events in the event mask
    * fd is not an inotify file descriptor
    OSError:
    * fd is not a valid file descriptor
    * Process has no access to specified file
    * File/Folder specified does not exist
    * Maximum number of watches hit
    MemoryError:
    * Raised if the kernel cannot allocate sufficent resources to handle the watch (eg kernel memory)
    """
    if hasattr(fd, "fileno"):
        fd = fd.fileno()
    assert isinstance(fd, int), "fd must by an integer"
    assert isinstance(path, basestring), "path is not a string"
    assert isinstance(mask, int), "mask must be an integer"

    wd = _C.inotify_add_watch(fd, path, mask)

    if wd < 0:
        err = _ffi.errno
        if err == _errno.EINVAL:
            raise ValueError("The event mask contains no valid events; or fd is not an inotify file descriptor")
        elif err == _errno.EACCES:
            raise OSError("You do not have permission to read the specified path")
        elif err == _errno.EBADF:
            raise OSError("fd is not a valid file descriptor")
        elif err == _errno.EFAULT:
            raise OSError("path points to a file/folder outside the processes accessible address space")
        elif err == _errno.ENOENT:
            raise OSError("File/Folder pointed to by path does not exist")
        elif err == _errno.ENOSPC:
            raise OSError("Maximum number of watches hit or insufficent kernel resources")
        elif err == _errno.ENOMEM:
            raise MemoryError("Insufficent kernel memory avalible")
        else:
            # If you are here, its a bug. send us the traceback
            raise ValueError("Unknown Error: {}".format(err))

    return wd

def inotify_rm_watch(fd, wd):
    """Stop watching a path for events

    Arguments:
    -----------
    fd: The inotify file descriptor to remove the watch from
    wd: The Watch to be removed

    Returns:
    ---------
    None

    Exceptions:
    ------------
    ValueError: Returned if supplied watch is not valid or if the file descriptor is not an inotify file descriptor
    OSError: File descriptor is invalid
    """
    ret = _C.inotify_rm_watch(fd, wd)

    if ret < 0:
        err = _ffi.errno
        if err == _errno.EINVAL:
            raise ValueError("wd is invalid or fd is not an inotify File Descriptor")
        elif err == _errno.EBADF:
            raise OSError("fd is not a valid file descriptor")
        else:
            # If you are here, its a bug. send us the traceback
            raise ValueError("Unknown Error: {}".format(err))

# Make the inotify flags more easily accessible by hoisting them out of the _C object
l = locals()
for key, value in _C.__dict__.iteritems():
    if key.startswith("IN_"):
        l[key] = value
# <_<
# >_>
# -_- <(This never happened, what you just saw was light reflecting off Venus)
del l
del key, value # python 2.x has vars escape from the scope of the loop, clean this up

def main():
    fd = inotify_init()
    if fd < 0:
        raise ValueError("syscall returned an error: {}({})".format(fd, _C.errno))
    inotify = fdopen(fd)

    wd = inotify_add_watch(fd, '/tmp', _C.IN_ALL_EVENTS)

    event_struct_size =  _ffi.sizeof('struct inotify_event')

    import select

    select.select([fd], [], [])
    l = _get_buffered_length(fd)

    event = inotify.read(l)
    print "bytes read:", len(event)
    str_buf = _ffi.new('char[]', len(event))
    str_buf[0:len(event)] = event
    event = _ffi.cast('struct inotify_event *', str_buf)
    print "Watch descriptor:", event.wd
    print "Event Mask:", event.mask
    print "Event Cookie:", event.cookie
    print "Event length:", event.len
    print "Event filename:", _ffi.string(str_buf[event_struct_size:event_struct_size+event.len])
#    _ffi.string(_ffi.cast("char *", event[event_struct_size:event_struct_size+event.len]))

if __name__ == "__main__":
    main()
<commit_msg>Looks like we actually need these<commit_after>#!/usr/bin/env python
"""inotify: Wrapper around the inotify syscalls providing both a function based and file like interface"""

from utils import get_buffered_length as _get_buffered_length
from select import select as _select
from os import read as _read
from cffi import FFI as _FFI
import errno as _errno

_ffi = _FFI()
_ffi.cdef("""
/*
 * struct inotify_event - structure read from the inotify device for each event
 *
 * When you are watching a directory, you will receive the filename for events
 * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd.
 */
struct inotify_event {
        int           wd;
        uint32_t      mask;
        uint32_t      cookie;
        uint32_t      len;
//        char          name[0];
};

/* the following are legal, implemented events that user-space can watch for */
#define IN_ACCESS        ...  /* File was accessed */
#define IN_MODIFY        ...  /* File was modified */
#define IN_ATTRIB        ...  /* Metadata changed */
#define IN_CLOSE_WRITE   ...  /* Writtable file was closed */
#define IN_CLOSE_NOWRITE ...  /* Unwrittable file closed */
#define IN_OPEN          ...  /* File was opened */
#define IN_MOVED_FROM    ...  /* File was moved from X */
#define IN_MOVED_TO      ...  /* File was moved to Y */
#define IN_CREATE        ...  /* Subfile was created */
#define IN_DELETE        ...  /* Subfile was deleted */
#define IN_DELETE_SELF   ...  /* Self was deleted */
#define IN_MOVE_SELF     ...  /* Self was moved */

/* the following are legal events.  they are sent as needed to any watch */
#define IN_UNMOUNT       ...  /* Backing fs was unmounted */
#define IN_Q_OVERFLOW    ...  /* Event queued overflowed */
#define IN_IGNORED       ...  /* File was ignored */

/* helper events */
#define IN_CLOSE         ...  /* close */
#define IN_MOVE          ...  /* moves */

/* special flags */
#define IN_ONLYDIR       ...  /* only watch the path if it is a directory */
#define IN_DONT_FOLLOW   ...  /* don't follow a sym link */
#define IN_EXCL_UNLINK   ...  /* exclude events on unlinked objects */
#define IN_MASK_ADD      ...  /* add to the mask of an already existing watch */
#define IN_ISDIR         ...  /* event occurred against dir */
#define IN_ONESHOT       ...  /* only send event once */

/*
 * All of the events - we build the list by hand so that we can add flags in
 * the future and not break backward compatibility.  Apps will get only the
 * events that they originally wanted.  Be sure to add new events here!
 */
#define IN_ALL_EVENTS  ...

/* Flags for sys_inotify_init1.  */
#define IN_CLOEXEC  ...
#define IN_NONBLOCK ...

int inotify_init(void);
int inotify_init1(int flags);
int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
int inotify_rm_watch(int fd, int wd);
""")

_C = _ffi.verify("""
#include <sys/inotify.h>
#include <sys/ioctl.h>
""", libraries=[])

def inotify_init(flags=0):
    """Initialise an inotify instnace and return a File Descriptor to refrence is

    Arguments:
    -----------
    Flags:
    -------
    IN_CLOEXEC: Automatically close the inotify handle on exec()
    IN_NONBLOCK: Place the file descriptor in non blocking mode
    """
    fd = _C.inotify_init1(flags)

    if fd < 0:
        err = _ffi.errno
        if err == _errno.EINVAL:
            raise ValueError("Invalid argument or flag")
        elif err == _errno.EMFILE:
            raise OSError("Maximum inotify instances reached")
        elif err == _errno.ENFILE:
            raise OSError("File descriptor limit hit")
        elif err == _errno.ENOMEM:
            raise MemoryError("Insufficent kernel memory avalible")
        else:
            # If you are here, its a bug. send us the traceback
            raise ValueError("Unknown Error: {}".format(err))

    return fd

def inotify_add_watch(fd, path, mask):
    """Start watching a filepath for events

    Arguments:
    -----------
    fd:    The inotify file descriptor to attach the watch to
    path:  The path to the file/directory to be monitored for events
    mask:  The events to listen for

    Flags:
    -------
    IN_ACCESS:        File was accessed
    IN_MODIFY:        File was modified
    IN_ATTRIB:        Metadata changed
    IN_CLOSE_WRITE:   Writtable file was closed
    IN_CLOSE_NOWRITE: Unwrittable file closed
    IN_OPEN:          File was opened
    IN_MOVED_FROM:    File was moved from X
    IN_MOVED_TO:      File was moved to Y
    IN_CREATE:        Subfile was created
    IN_DELETE:        Subfile was deleted
    IN_DELETE_SELF:   Self was deleted
    IN_MOVE_SELF:     Self was moved

    IN_ONLYDIR:      only watch the path if it is a directory
    IN_DONT_FOLLOW:  don't follow a sym link
    IN_EXCL_UNLINK:  exclude events on unlinked objects
    IN_MASK_ADD:     add to the mask of an already existing watch
    IN_ISDIR:        event occurred against dir
    IN_ONESHOT:      only send event once

    Returns:
    ---------
    int: A watch descriptor that can be passed to inotify_rm_watch

    Exceptions:
    ------------
    ValueError:
    * No valid events in the event mask
    * fd is not an inotify file descriptor
    OSError:
    * fd is not a valid file descriptor
    * Process has no access to specified file
    * File/Folder specified does not exist
    * Maximum number of watches hit
    MemoryError:
    * Raised if the kernel cannot allocate sufficent resources to handle the watch (eg kernel memory)
    """
    if hasattr(fd, "fileno"):
        fd = fd.fileno()
    assert isinstance(fd, int), "fd must by an integer"
    assert isinstance(path, basestring), "path is not a string"
    assert isinstance(mask, int), "mask must be an integer"

    wd = _C.inotify_add_watch(fd, path, mask)

    if wd < 0:
        err = _ffi.errno
        if err == _errno.EINVAL:
            raise ValueError("The event mask contains no valid events; or fd is not an inotify file descriptor")
        elif err == _errno.EACCES:
            raise OSError("You do not have permission to read the specified path")
        elif err == _errno.EBADF:
            raise OSError("fd is not a valid file descriptor")
        elif err == _errno.EFAULT:
            raise OSError("path points to a file/folder outside the processes accessible address space")
        elif err == _errno.ENOENT:
            raise OSError("File/Folder pointed to by path does not exist")
        elif err == _errno.ENOSPC:
            raise OSError("Maximum number of watches hit or insufficent kernel resources")
        elif err == _errno.ENOMEM:
            raise MemoryError("Insufficent kernel memory avalible")
        else:
            # If you are here, its a bug. send us the traceback
            raise ValueError("Unknown Error: {}".format(err))

    return wd

def inotify_rm_watch(fd, wd):
    """Stop watching a path for events

    Arguments:
    -----------
    fd: The inotify file descriptor to remove the watch from
    wd: The Watch to be removed

    Returns:
    ---------
    None

    Exceptions:
    ------------
    ValueError: Returned if supplied watch is not valid or if the file descriptor is not an inotify file descriptor
    OSError: File descriptor is invalid
    """
    ret = _C.inotify_rm_watch(fd, wd)

    if ret < 0:
        err = _ffi.errno
        if err == _errno.EINVAL:
            raise ValueError("wd is invalid or fd is not an inotify File Descriptor")
        elif err == _errno.EBADF:
            raise OSError("fd is not a valid file descriptor")
        else:
            # If you are here, its a bug. send us the traceback
            raise ValueError("Unknown Error: {}".format(err))

# Make the inotify flags more easily accessible by hoisting them out of the _C object
l = locals()
for key, value in _C.__dict__.iteritems():
    if key.startswith("IN_"):
        l[key] = value
# <_<
# >_>
# -_- <(This never happened, what you just saw was light reflecting off Venus)
del l
del key, value # python 2.x has vars escape from the scope of the loop, clean this up

def main():
    fd = inotify_init()
    if fd < 0:
        raise ValueError("syscall returned an error: {}({})".format(fd, _C.errno))
    inotify = fdopen(fd)

    wd = inotify_add_watch(fd, '/tmp', _C.IN_ALL_EVENTS)

    event_struct_size =  _ffi.sizeof('struct inotify_event')

    import select

    select.select([fd], [], [])
    l = _get_buffered_length(fd)

    event = inotify.read(l)
    print "bytes read:", len(event)
    str_buf = _ffi.new('char[]', len(event))
    str_buf[0:len(event)] = event
    event = _ffi.cast('struct inotify_event *', str_buf)
    print "Watch descriptor:", event.wd
    print "Event Mask:", event.mask
    print "Event Cookie:", event.cookie
    print "Event length:", event.len
    print "Event filename:", _ffi.string(str_buf[event_struct_size:event_struct_size+event.len])
#    _ffi.string(_ffi.cast("char *", event[event_struct_size:event_struct_size+event.len]))

if __name__ == "__main__":
    main()
<|endoftext|>