Commit 7ac5bef1 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki

Fix code formatting to conform to the PEP 8 style guide

parent 65f9b8fd
#!/usr/bin/env python #!/usr/bin/env python
# Copyright 2011-2012 GRNET S.A. All rights reserved. # Copyright 2011-2012 GRNET S.A. All rights reserved.
# #
# Redistribution and use in source and binary forms, with or # Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following # without modification, are permitted provided that the following
# conditions are met: # conditions are met:
# #
# 1. Redistributions of source code must retain the above # 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following # copyright notice, this list of conditions and the following
# disclaimer. # disclaimer.
# #
# 2. Redistributions in binary form must reproduce the above # 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following # copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials # disclaimer in the documentation and/or other materials
# provided with the distribution. # provided with the distribution.
# #
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
# #
# The views and conclusions contained in the software and # The views and conclusions contained in the software and
# documentation are those of the authors and should not be # documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed # interpreted as representing official policies, either expressed
...@@ -41,41 +41,43 @@ from django.conf import settings ...@@ -41,41 +41,43 @@ from django.conf import settings
from pithos.backends.modular import ModularBackend from pithos.backends.modular import ModularBackend
class Migration(object): class Migration(object):
def __init__(self, db): def __init__(self, db):
self.engine = create_engine(db) self.engine = create_engine(db)
self.metadata = MetaData(self.engine) self.metadata = MetaData(self.engine)
#self.engine.echo = True #self.engine.echo = True
self.conn = self.engine.connect() self.conn = self.engine.connect()
options = getattr(settings, 'BACKEND', None)[1] options = getattr(settings, 'BACKEND', None)[1]
self.backend = ModularBackend(*options) self.backend = ModularBackend(*options)
def execute(self): def execute(self):
pass pass
class Cache(): class Cache():
def __init__(self, db): def __init__(self, db):
self.engine = create_engine(db) self.engine = create_engine(db)
metadata = MetaData(self.engine) metadata = MetaData(self.engine)
columns=[] columns = []
columns.append(Column('path', String(2048), primary_key=True)) columns.append(Column('path', String(2048), primary_key=True))
columns.append(Column('hash', String(255))) columns.append(Column('hash', String(255)))
self.files = Table('files', metadata, *columns) self.files = Table('files', metadata, *columns)
self.conn = self.engine.connect() self.conn = self.engine.connect()
self.engine.echo = True self.engine.echo = True
metadata.create_all(self.engine) metadata.create_all(self.engine)
def put(self, path, hash): def put(self, path, hash):
# Insert or replace. # Insert or replace.
s = self.files.delete().where(self.files.c.path==path) s = self.files.delete().where(self.files.c.path == path)
r = self.conn.execute(s) r = self.conn.execute(s)
r.close() r.close()
s = self.files.insert() s = self.files.insert()
r = self.conn.execute(s, {'path': path, 'hash': hash}) r = self.conn.execute(s, {'path': path, 'hash': hash})
r.close() r.close()
def get(self, path): def get(self, path):
s = select([self.files.c.hash], self.files.c.path == path) s = select([self.files.c.hash], self.files.c.path == path)
r = self.conn.execute(s) r = self.conn.execute(s)
......
...@@ -144,17 +144,17 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, ...@@ -144,17 +144,17 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
except ImportError: except ImportError:
return _do_download(version, download_base, to_dir, download_delay) return _do_download(version, download_base, to_dir, download_delay)
try: try:
pkg_resources.require("distribute>="+version) pkg_resources.require("distribute>=" + version)
return return
except pkg_resources.VersionConflict: except pkg_resources.VersionConflict:
e = sys.exc_info()[1] e = sys.exc_info()[1]
if was_imported: if was_imported:
sys.stderr.write( sys.stderr.write(
"The required version of distribute (>=%s) is not available,\n" "The required version of distribute (>=%s) is not available,\n"
"and can't be installed while this script is running. Please\n" "and can't be installed while this script is running. Please\n"
"install a more recent version first, using\n" "install a more recent version first, using\n"
"'easy_install -U distribute'." "'easy_install -U distribute'."
"\n\n(Currently using %r)\n" % (version, e.args[0])) "\n\n(Currently using %r)\n" % (version, e.args[0]))
sys.exit(2) sys.exit(2)
else: else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok del pkg_resources, sys.modules['pkg_resources'] # reload ok
...@@ -167,6 +167,7 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, ...@@ -167,6 +167,7 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
if not no_fake: if not no_fake:
_create_fake_setuptools_pkg_info(to_dir) _create_fake_setuptools_pkg_info(to_dir)
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, delay=15): to_dir=os.curdir, delay=15):
"""Download distribute from a specified location and return its filename """Download distribute from a specified location and return its filename
...@@ -203,6 +204,7 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, ...@@ -203,6 +204,7 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
dst.close() dst.close()
return os.path.realpath(saveto) return os.path.realpath(saveto)
def _no_sandbox(function): def _no_sandbox(function):
def __no_sandbox(*args, **kw): def __no_sandbox(*args, **kw):
try: try:
...@@ -227,6 +229,7 @@ def _no_sandbox(function): ...@@ -227,6 +229,7 @@ def _no_sandbox(function):
return __no_sandbox return __no_sandbox
def _patch_file(path, content): def _patch_file(path, content):
"""Will backup the file then patch it""" """Will backup the file then patch it"""
existing_content = open(path).read() existing_content = open(path).read()
...@@ -245,15 +248,18 @@ def _patch_file(path, content): ...@@ -245,15 +248,18 @@ def _patch_file(path, content):
_patch_file = _no_sandbox(_patch_file) _patch_file = _no_sandbox(_patch_file)
def _same_content(path, content): def _same_content(path, content):
return open(path).read() == content return open(path).read() == content
def _rename_path(path): def _rename_path(path):
new_name = path + '.OLD.%s' % time.time() new_name = path + '.OLD.%s' % time.time()
log.warn('Renaming %s into %s', path, new_name) log.warn('Renaming %s into %s', path, new_name)
os.rename(path, new_name) os.rename(path, new_name)
return new_name return new_name
def _remove_flat_installation(placeholder): def _remove_flat_installation(placeholder):
if not os.path.isdir(placeholder): if not os.path.isdir(placeholder):
log.warn('Unkown installation at %s', placeholder) log.warn('Unkown installation at %s', placeholder)
...@@ -289,18 +295,20 @@ def _remove_flat_installation(placeholder): ...@@ -289,18 +295,20 @@ def _remove_flat_installation(placeholder):
_remove_flat_installation = _no_sandbox(_remove_flat_installation) _remove_flat_installation = _no_sandbox(_remove_flat_installation)
def _after_install(dist): def _after_install(dist):
log.warn('After install bootstrap.') log.warn('After install bootstrap.')
placeholder = dist.get_command_obj('install').install_purelib placeholder = dist.get_command_obj('install').install_purelib
_create_fake_setuptools_pkg_info(placeholder) _create_fake_setuptools_pkg_info(placeholder)
def _create_fake_setuptools_pkg_info(placeholder): def _create_fake_setuptools_pkg_info(placeholder):
if not placeholder or not os.path.exists(placeholder): if not placeholder or not os.path.exists(placeholder):
log.warn('Could not find the install location') log.warn('Could not find the install location')
return return
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
setuptools_file = 'setuptools-%s-py%s.egg-info' % \ setuptools_file = 'setuptools-%s-py%s.egg-info' % \
(SETUPTOOLS_FAKED_VERSION, pyver) (SETUPTOOLS_FAKED_VERSION, pyver)
pkg_info = os.path.join(placeholder, setuptools_file) pkg_info = os.path.join(placeholder, setuptools_file)
if os.path.exists(pkg_info): if os.path.exists(pkg_info):
log.warn('%s already exists', pkg_info) log.warn('%s already exists', pkg_info)
...@@ -321,7 +329,9 @@ def _create_fake_setuptools_pkg_info(placeholder): ...@@ -321,7 +329,9 @@ def _create_fake_setuptools_pkg_info(placeholder):
finally: finally:
f.close() f.close()
_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) _create_fake_setuptools_pkg_info = _no_sandbox(
_create_fake_setuptools_pkg_info)
def _patch_egg_dir(path): def _patch_egg_dir(path):
# let's check if it's already patched # let's check if it's already patched
...@@ -343,6 +353,7 @@ def _patch_egg_dir(path): ...@@ -343,6 +353,7 @@ def _patch_egg_dir(path):
_patch_egg_dir = _no_sandbox(_patch_egg_dir) _patch_egg_dir = _no_sandbox(_patch_egg_dir)
def _before_install(): def _before_install():
log.warn('Before install bootstrap.') log.warn('Before install bootstrap.')
_fake_setuptools() _fake_setuptools()
...@@ -351,7 +362,7 @@ def _before_install(): ...@@ -351,7 +362,7 @@ def _before_install():
def _under_prefix(location): def _under_prefix(location):
if 'install' not in sys.argv: if 'install' not in sys.argv:
return True return True
args = sys.argv[sys.argv.index('install')+1:] args = sys.argv[sys.argv.index('install') + 1:]
for index, arg in enumerate(args): for index, arg in enumerate(args):
for option in ('--root', '--prefix'): for option in ('--root', '--prefix'):
if arg.startswith('%s=' % option): if arg.startswith('%s=' % option):
...@@ -359,7 +370,7 @@ def _under_prefix(location): ...@@ -359,7 +370,7 @@ def _under_prefix(location):
return location.startswith(top_dir) return location.startswith(top_dir)
elif arg == option: elif arg == option:
if len(args) > index: if len(args) > index:
top_dir = args[index+1] top_dir = args[index + 1]
return location.startswith(top_dir) return location.startswith(top_dir)
if arg == '--user' and USER_SITE is not None: if arg == '--user' and USER_SITE is not None:
return location.startswith(USER_SITE) return location.startswith(USER_SITE)
...@@ -380,7 +391,8 @@ def _fake_setuptools(): ...@@ -380,7 +391,8 @@ def _fake_setuptools():
replacement=False)) replacement=False))
except TypeError: except TypeError:
# old distribute API # old distribute API
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) setuptools_dist = ws.find(
pkg_resources.Requirement.parse('setuptools'))
if setuptools_dist is None: if setuptools_dist is None:
log.warn('No setuptools distribution found') log.warn('No setuptools distribution found')
...@@ -406,7 +418,7 @@ def _fake_setuptools(): ...@@ -406,7 +418,7 @@ def _fake_setuptools():
log.warn('Egg installation') log.warn('Egg installation')
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
if (os.path.exists(pkg_info) and if (os.path.exists(pkg_info) and
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)): _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
log.warn('Already patched.') log.warn('Already patched.')
return return
log.warn('Patching...') log.warn('Patching...')
...@@ -448,7 +460,7 @@ def _extractall(self, path=".", members=None): ...@@ -448,7 +460,7 @@ def _extractall(self, path=".", members=None):
# Extract directories with a safe mode. # Extract directories with a safe mode.
directories.append(tarinfo) directories.append(tarinfo)
tarinfo = copy.copy(tarinfo) tarinfo = copy.copy(tarinfo)
tarinfo.mode = 448 # decimal for oct 0700 tarinfo.mode = 448 # decimal for oct 0700
self.extract(tarinfo, path) self.extract(tarinfo, path)
# Reverse sort directories. # Reverse sort directories.
......
# Copyright 2011-2012 GRNET S.A. All rights reserved. # Copyright 2011-2012 GRNET S.A. All rights reserved.
# #
# Redistribution and use in source and binary forms, with or # Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following # without modification, are permitted provided that the following
# conditions are met: # conditions are met:
# #
# 1. Redistributions of source code must retain the above # 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following # copyright notice, this list of conditions and the following
# disclaimer. # disclaimer.
# #
# 2. Redistributions in binary form must reproduce the above # 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following # copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials # disclaimer in the documentation and/or other materials
# provided with the distribution. # provided with the distribution.
# #
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
# #
# The views and conclusions contained in the software and # The views and conclusions contained in the software and
# documentation are those of the authors and should not be # documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed # interpreted as representing official policies, either expressed
...@@ -46,12 +46,13 @@ from pithos.api.settings import AUTHENTICATION_URL, AUTHENTICATION_USERS, SERVIC ...@@ -46,12 +46,13 @@ from pithos.api.settings import AUTHENTICATION_URL, AUTHENTICATION_USERS, SERVIC
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def delegate_to_login_service(request): def delegate_to_login_service(request):
url = AUTHENTICATION_URL url = AUTHENTICATION_URL
users = AUTHENTICATION_USERS users = AUTHENTICATION_USERS
if users or not url: if users or not url:
return HttpResponseNotFound() return HttpResponseNotFound()
p = urlparse(url) p = urlparse(url)
if request.is_secure(): if request.is_secure():
proto = 'https://' proto = 'https://'
...@@ -61,21 +62,22 @@ def delegate_to_login_service(request): ...@@ -61,21 +62,22 @@ def delegate_to_login_service(request):
uri = proto + p.netloc + '/login?' + urlencode(params) uri = proto + p.netloc + '/login?' + urlencode(params)
return HttpResponseRedirect(uri) return HttpResponseRedirect(uri)
@csrf_exempt @csrf_exempt
def delegate_to_feedback_service(request): def delegate_to_feedback_service(request):
url = AUTHENTICATION_URL url = AUTHENTICATION_URL
users = AUTHENTICATION_USERS users = AUTHENTICATION_USERS
if users or not url: if users or not url:
return HttpResponseNotFound() return HttpResponseNotFound()
p = urlparse(url) p = urlparse(url)
if request.is_secure(): if request.is_secure():
proto = 'https://' proto = 'https://'
else: else:
proto = 'http://' proto = 'http://'
uri = proto + p.netloc + '/im/service/api/v2.0/feedback' uri = proto + p.netloc + '/im/service/api/v2.0/feedback'
headers = { 'X-Auth-Token' : SERVICE_TOKEN } headers = {'X-Auth-Token': SERVICE_TOKEN}
values = dict([(k, v) for k, v in request.POST.items()]) values = dict([(k, v) for k, v in request.POST.items()])
data = urllib.urlencode(values) data = urllib.urlencode(values)
req = urllib2.Request(uri, data, headers) req = urllib2.Request(uri, data, headers)
...@@ -87,4 +89,4 @@ def delegate_to_feedback_service(request): ...@@ -87,4 +89,4 @@ def delegate_to_feedback_service(request):
except urllib2.URLError, e: except urllib2.URLError, e:
logger.exception(e) logger.exception(e)
return HttpResponse(status=e.reason) return HttpResponse(status=e.reason)
return HttpResponse() return HttpResponse()
\ No newline at end of file
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION, from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH, BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
BACKEND_BLOCK_UMASK, BACKEND_BLOCK_UMASK,
BACKEND_QUEUE_MODULE, BACKEND_QUEUE_CONNECTION, BACKEND_QUEUE_MODULE, BACKEND_QUEUE_CONNECTION,
BACKEND_QUOTA, BACKEND_VERSIONING) BACKEND_QUOTA, BACKEND_VERSIONING)
from pithos.backends import connect_backend from pithos.backends import connect_backend
from pithos.api.util import hashmap_md5 from pithos.api.util import hashmap_md5
...@@ -14,10 +14,11 @@ from astakos.im.settings import DEFAULT_FROM_EMAIL ...@@ -14,10 +14,11 @@ from astakos.im.settings import DEFAULT_FROM_EMAIL
import socket import socket
from smtplib import SMTPException from smtplib import SMTPException
def update_md5(m): def update_md5(m):
if m['resource'] != 'object' or m['details']['action'] != 'object update': if m['resource'] != 'object' or m['details']['action'] != 'object update':
return return
backend = connect_backend(db_module=BACKEND_DB_MODULE, backend = connect_backend(db_module=BACKEND_DB_MODULE,
db_connection=BACKEND_DB_CONNECTION, db_connection=BACKEND_DB_CONNECTION,
block_module=BACKEND_BLOCK_MODULE, block_module=BACKEND_BLOCK_MODULE,
...@@ -27,38 +28,44 @@ def update_md5(m): ...@@ -27,38 +28,44 @@ def update_md5(m):
queue_connection=BACKEND_QUEUE_CONNECTION) queue_connection=BACKEND_QUEUE_CONNECTION)
backend.default_policy['quota'] = BACKEND_QUOTA backend.default_policy['quota'] = BACKEND_QUOTA
backend.default_policy['versioning'] = BACKEND_VERSIONING backend.default_policy['versioning'] = BACKEND_VERSIONING
path = m['value'] path = m['value']
account, container, name = path.split('/', 2) account, container, name = path.split('/', 2)
version = m['details']['version'] version = m['details']['version']
meta = None meta = None
try: try:
meta = backend.get_object_meta(account, account, container, name, 'pithos', version) meta = backend.get_object_meta(
account, account, container, name, 'pithos', version)
if meta['checksum'] == '': if meta['checksum'] == '':
size, hashmap = backend.get_object_hashmap(account, account, container, name, version) size, hashmap = backend.get_object_hashmap(
account, account, container, name, version)
checksum = hashmap_md5(backend, hashmap, size) checksum = hashmap_md5(backend, hashmap, size)
backend.update_object_checksum(account, account, container, name, version, checksum) backend.update_object_checksum(
account, account, container, name, version, checksum)
print 'INFO: Updated checksum for path "%s"' % (path,) print 'INFO: Updated checksum for path "%s"' % (path,)
except Exception, e: except Exception, e:
print 'WARNING: Can not update checksum for path "%s" (%s)' % (path, e) print 'WARNING: Can not update checksum for path "%s" (%s)' % (path, e)
backend.close() backend.close()
def send_sharing_notification(m): def send_sharing_notification(m):
if m['resource'] != 'sharing': if m['resource'] != 'sharing':
return return
members = m['details']['members'] members = m['details']['members']
user = m['details']['user'] user = m['details']['user']
path = m['value'] path = m['value']
account, container, name = path.split('/', 2) account, container, name = path.split('/', 2)
subject = 'Invitation to a Pithos+ shared object' subject = 'Invitation to a Pithos+ shared object'
from_email = DEFAULT_FROM_EMAIL from_email = DEFAULT_FROM_EMAIL
recipient_list = members recipient_list = members
message = 'User %s has invited you to a Pithos+ shared object. You can view it under "Shared to me" at "%s".' %(user, path) message = 'User %s has invited you to a Pithos+ shared object. You can view it under "Shared to me" at "%s".' % (user, path)
try: try:
send_mail(subject, message, from_email, recipient_list) send_mail(subject, message, from_email, recipient_list)
print 'INFO: Sharing notification sent for path "%s" to %s' % (path, ','.join(recipient_list)) print 'INFO: Sharing notification sent for path "%s" to %s' % (
path, ','.join(recipient_list))
except (SMTPException, socket.error) as e: except (SMTPException, socket.error) as e:
print 'WARNING: Can not update send email for sharing "%s" (%s)' % (path, e) print 'WARNING: Can not update send email for sharing "%s" (%s)' % (
path, e)
# Copyright 2011-2012 GRNET S.A. All rights reserved. # Copyright 2011-2012 GRNET S.A. All rights reserved.
# #
# Redistribution and use in source and binary forms, with or # Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following # without modification, are permitted provided that the following
# conditions are met: # conditions are met:
# #
# 1. Redistributions of source code must retain the above # 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following # copyright notice, this list of conditions and the following
# disclaimer. # disclaimer.
# #
# 2. Redistributions in binary form must reproduce the above # 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following # copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials # disclaimer in the documentation and/or other materials
# provided with the distribution. # provided with the distribution.