From 6bfffeee138ea88ab3c9e84d0965ca193c2c4647 Mon Sep 17 00:00:00 2001 From: Joseph Nuthalapati Date: Sun, 9 Dec 2018 21:34:18 +0530 Subject: [PATCH] calibre: Add new e-book library app [joseph: initial code for the app] Signed-off-by: Joseph Nuthalapati [sunil: use the modified framework API] [sunil: simplify setup logic, move to service file] [sunil: strict security for service file, dynamic users] [sunil: interface for managing libraries] [sunil: implement backup/restore] [sunil: add functional, action, and view tests] [sunil: use svg icon] [sunil: update description] [sunil: fix apache configuration] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Joseph Nuthalapati --- actions/calibre | 76 ++ plinth/modules/calibre/__init__.py | 124 ++ .../conf-available/calibre-freedombox.conf | 10 + .../data/etc/plinth/modules-enabled/calibre | 1 + .../system/calibre-server-freedombox.service | 47 + plinth/modules/calibre/forms.py | 29 + plinth/modules/calibre/manifest.py | 21 + .../templates/calibre-delete-library.html | 30 + plinth/modules/calibre/templates/calibre.html | 59 + plinth/modules/calibre/tests/__init__.py | 0 plinth/modules/calibre/tests/data/sample.txt | 1 + plinth/modules/calibre/tests/test_actions.py | 84 ++ .../modules/calibre/tests/test_functional.py | 180 +++ plinth/modules/calibre/tests/test_views.py | 146 +++ plinth/modules/calibre/urls.py | 16 + plinth/modules/calibre/views.py | 75 ++ plinth/templates/form.html | 22 + pytest.ini | 1 + static/themes/default/icons/calibre.png | Bin 0 -> 56951 bytes static/themes/default/icons/calibre.svg | 1139 +++++++++++++++++ 20 files changed, 2061 insertions(+) create mode 100755 actions/calibre create mode 100644 plinth/modules/calibre/__init__.py create mode 100644 plinth/modules/calibre/data/etc/apache2/conf-available/calibre-freedombox.conf create mode 100644 plinth/modules/calibre/data/etc/plinth/modules-enabled/calibre create mode 100644 plinth/modules/calibre/data/lib/systemd/system/calibre-server-freedombox.service create mode 100644 plinth/modules/calibre/forms.py create mode 100644 plinth/modules/calibre/manifest.py create mode 100644 plinth/modules/calibre/templates/calibre-delete-library.html create mode 100644 plinth/modules/calibre/templates/calibre.html create mode 100644 plinth/modules/calibre/tests/__init__.py create mode 100644 plinth/modules/calibre/tests/data/sample.txt create mode 100644 plinth/modules/calibre/tests/test_actions.py create mode 100644 plinth/modules/calibre/tests/test_functional.py create mode 100644 plinth/modules/calibre/tests/test_views.py create mode 100644 plinth/modules/calibre/urls.py create mode 100644 plinth/modules/calibre/views.py create mode 100644 plinth/templates/form.html create mode 100644 static/themes/default/icons/calibre.png create mode 100644 static/themes/default/icons/calibre.svg diff --git a/actions/calibre b/actions/calibre new file mode 100755 index 000000000..bc8d51cd7 --- /dev/null +++ b/actions/calibre @@ -0,0 +1,76 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Configuration helper for calibre. +""" + +import argparse +import json +import pathlib +import shutil +import subprocess + +from plinth.modules import calibre + +LIBRARIES_PATH = pathlib.Path('/var/lib/calibre-server-freedombox/libraries') + + +def parse_arguments(): + """Return parsed command line arguments as dictionary.""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + subparsers.add_parser('list-libraries', + help='Return the list of libraries setup') + subparser = subparsers.add_parser('create-library', + help='Create an empty library') + subparser.add_argument('name', help='Name of the new library') + subparser = subparsers.add_parser('delete-library', + help='Delete a library and its contents') + subparser.add_argument('name', help='Name of the library to delete') + + subparsers.required = True + return parser.parse_args() + + +def subcommand_list_libraries(_): + """Return the list of libraries setup.""" + libraries = [] + for library in LIBRARIES_PATH.glob('*/metadata.db'): + libraries.append(str(library.parent.name)) + + print(json.dumps({'libraries': libraries})) + + +def subcommand_create_library(arguments): + """Create an empty library.""" + calibre.validate_library_name(arguments.name) + library = LIBRARIES_PATH / arguments.name + library.mkdir(mode=0o755) # Raise exception if already exists + subprocess.call( + ['calibredb', '--with-library', library, 'list_categories'], + stdout=subprocess.DEVNULL) + + # Force systemd StateDirectory= logic to assign proper ownership to the + # DynamicUser= + shutil.chown(LIBRARIES_PATH.parent, 'root', 'root') + + +def subcommand_delete_library(arguments): + """Delete a library and its contents.""" + calibre.validate_library_name(arguments.name) + library = LIBRARIES_PATH / arguments.name + shutil.rmtree(library) + + +def main(): + """Parse arguments and perform all duties.""" + arguments = parse_arguments() + + subcommand = arguments.subcommand.replace('-', '_') + subcommand_method = globals()['subcommand_' + subcommand] + subcommand_method(arguments) + + +if __name__ == '__main__': + main() diff --git a/plinth/modules/calibre/__init__.py b/plinth/modules/calibre/__init__.py new file mode 100644 index 000000000..e3786812c --- /dev/null +++ b/plinth/modules/calibre/__init__.py @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +FreedomBox app for calibre e-book library. +""" + +import json +import re + +from django.utils.translation import ugettext_lazy as _ + +from plinth import actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu +from plinth.daemon import Daemon +from plinth.modules.apache.components import Webserver +from plinth.modules.firewall.components import Firewall +from plinth.modules.users.components import UsersAndGroups +from plinth.utils import format_lazy + +from .manifest import backup, clients # noqa, pylint: disable=unused-import + +version = 1 + +managed_services = ['calibre-server-freedombox'] + +managed_packages = ['calibre'] + +_description = [ + format_lazy( + _('calibre server provides online access to your e-book collection. ' + 'You can store your e-books on your {box_name}, read them online or ' + 'from any of your devices.'), box_name=_(cfg.box_name)), + _('You can organize your e-books, extract and edit their metadata, and ' + 'perform advanced search. It can import, export, or convert across a ' + 'wide range of formats to make e-books ready for reading on any ' + 'device. You can read books on your browser with the builtin web ' + 'reader. It remembers your last read location, bookmarks, and ' + 'highlighted text.'), + _('Only users belonging to calibre group will be able to access ' + 'the app. Content distribution with OPDS is currently not supported.') +] + +app = None + +LIBRARY_NAME_PATTERN = r'[a-zA-Z0-9 _-]+' + + +class CalibreApp(app_module.App): + """FreedomBox app for calibre.""" + + app_id = 'calibre' + + def __init__(self): + """Create components for the app.""" + super().__init__() + + groups = {'calibre': _('Administer calibre application')} + + info = app_module.Info(app_id=self.app_id, version=version, + name=_('calibre'), icon_filename='calibre', + short_description=_('E-book Library'), + description=_description, manual_page='calibre', + clients=clients) + self.add(info) + + menu_item = menu.Menu('menu-calibre', info.name, + info.short_description, info.icon_filename, + 'calibre:index', parent_url_name='apps') + self.add(menu_item) + + shortcut = frontpage.Shortcut('shortcut-calibre', info.name, + short_description=info.short_description, + icon=info.icon_filename, url='/calibre', + clients=info.clients, + login_required=True, + allowed_groups=list(groups)) + self.add(shortcut) + + firewall = Firewall('firewall-calibre', info.name, + ports=['http', 'https'], is_external=True) + self.add(firewall) + + webserver = Webserver('webserver-calibre', 'calibre-freedombox', + urls=['https://{host}/calibre']) + self.add(webserver) + + daemon = Daemon('daemon-calibre', managed_services[0], + listen_ports=[(8844, 'tcp4')]) + self.add(daemon) + + users_and_groups = UsersAndGroups('users-and-groups-calibre', + reserved_usernames=['calibre'], + groups=groups) + self.add(users_and_groups) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(managed_packages) + helper.call('post', app.enable) + + +def validate_library_name(library_name): + """Raise exception if library name does not fit the accepted pattern.""" + if not re.fullmatch(r'[A-Za-z0-9_.-]+', library_name): + raise Exception('Invalid library name') + + +def list_libraries(): + """Return a list of libraries.""" + output = actions.superuser_run('calibre', ['list-libraries']) + return json.loads(output)['libraries'] + + +def create_library(name): + """Create an empty library.""" + actions.superuser_run('calibre', ['create-library', name]) + actions.superuser_run('service', ['try-restart', managed_services[0]]) + + +def delete_library(name): + """Delete a library and its contents.""" + actions.superuser_run('calibre', ['delete-library', name]) + actions.superuser_run('service', ['try-restart', managed_services[0]]) diff --git a/plinth/modules/calibre/data/etc/apache2/conf-available/calibre-freedombox.conf b/plinth/modules/calibre/data/etc/apache2/conf-available/calibre-freedombox.conf new file mode 100644 index 000000000..e423f2134 --- /dev/null +++ b/plinth/modules/calibre/data/etc/apache2/conf-available/calibre-freedombox.conf @@ -0,0 +1,10 @@ +## +## On all sites, provide calibre web interface on a path: /calibre +## + + ProxyPass http://localhost:8844/calibre + Include includes/freedombox-single-sign-on.conf + + TKTAuthToken "admin" "calibre" + + diff --git a/plinth/modules/calibre/data/etc/plinth/modules-enabled/calibre b/plinth/modules/calibre/data/etc/plinth/modules-enabled/calibre new file mode 100644 index 000000000..bef75a6fd --- /dev/null +++ b/plinth/modules/calibre/data/etc/plinth/modules-enabled/calibre @@ -0,0 +1 @@ +plinth.modules.calibre diff --git a/plinth/modules/calibre/data/lib/systemd/system/calibre-server-freedombox.service b/plinth/modules/calibre/data/lib/systemd/system/calibre-server-freedombox.service new file mode 100644 index 000000000..29941bc85 --- /dev/null +++ b/plinth/modules/calibre/data/lib/systemd/system/calibre-server-freedombox.service @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later + +[Unit] +Description=calibre Content Server +Documentation=man:calibre-server(1) +After=network.target + +[Service] +CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_NET_ADMIN CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_KILL CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_LINUX_IMMUTABLE CAP_IPC_LOCK CAP_SYS_CHROOT CAP_BLOCK_SUSPEND CAP_LEASE CAP_SYS_PACCT CAP_SYS_TTY_CONFIG CAP_SYS_BOOT CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_SYS_NICE CAP_SYS_RESOURCE +DevicePolicy=closed +Environment=HOME="/var/lib/calibre-server-freedombox" +Environment=DEFAULT_LIBRARY="/var/lib/calibre-server-freedombox/libraries/Library" +Environment=ARGS="--listen-on 127.0.0.1 --url-prefix /calibre --port 8844 --enable-local-write --disable-auth" +ExecStartPre=sh -e -c "files=$$(ls ${HOME}/libraries/*/metadata.db 2>/dev/null || true); [ \"x$${files}\" = \"x\" ] && (mkdir -p \"${DEFAULT_LIBRARY}\" && calibredb --with-library=\"${DEFAULT_LIBRARY}\" list_categories > /dev/null) || true" +ExecStart=sh -e -c "files=${HOME}/libraries/*/metadata.db; libraries=$$(dirname $${files}) ; exec /usr/bin/calibre-server $ARGS $${libraries}" +Restart=on-failure +ExecReload=/bin/kill -HUP $MAINPID +DynamicUser=yes +LockPersonality=yes +NoNewPrivileges=yes +PrivateDevices=yes +PrivateMounts=yes +PrivateTmp=yes +PrivateUsers=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +StateDirectory=calibre-server-freedombox +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@resources +SystemCallFilter=~@privileged +SystemCallErrorNumber=EPERM +Type=simple + + +[Install] +WantedBy=multi-user.target diff --git a/plinth/modules/calibre/forms.py b/plinth/modules/calibre/forms.py new file mode 100644 index 000000000..61fe67d61 --- /dev/null +++ b/plinth/modules/calibre/forms.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Django form for configuring calibre. +""" + +from django import forms +from django.core import validators +from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ + +from plinth.modules import calibre + + +class CreateLibraryForm(forms.Form): + """Form to create an empty library.""" + + name = forms.CharField( + label=_('Name of the new library'), strip=True, + validators=[validators.RegexValidator(r'^[A-Za-z0-9_.-]+$')]) + + def clean_name(self): + """Check if the library name is valid.""" + name = self.cleaned_data['name'] + + if name in calibre.list_libraries(): + raise ValidationError( + _('A library with this name already exists.')) + + return name diff --git a/plinth/modules/calibre/manifest.py b/plinth/modules/calibre/manifest.py new file mode 100644 index 000000000..2b6ce7317 --- /dev/null +++ b/plinth/modules/calibre/manifest.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later + +from django.utils.translation import ugettext_lazy as _ + +from plinth.clients import validate +from plinth.modules.backups.api import validate as validate_backup + +clients = validate([{ + 'name': _('calibre'), + 'platforms': [{ + 'type': 'web', + 'url': '/calibre/' + }] +}]) + +backup = validate_backup({ + 'data': { + 'directories': ['/var/lib/private/calibre-server-freedombox/'] + }, + 'services': ['calibre-server-freedombox'] +}) diff --git a/plinth/modules/calibre/templates/calibre-delete-library.html b/plinth/modules/calibre/templates/calibre-delete-library.html new file mode 100644 index 000000000..93946e251 --- /dev/null +++ b/plinth/modules/calibre/templates/calibre-delete-library.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% comment %} +# SPDX-License-Identifier: AGPL-3.0-or-later +{% endcomment %} + +{% load i18n %} + +{% block content %} + +

+ {% blocktrans trimmed %} + Delete calibre Library {{ name }} + {% endblocktrans %} +

+ +

+ {% blocktrans trimmed %} + Delete this library permanently? All stored e-books and saved data will be + lost. + {% endblocktrans %} +

+ +
+ {% csrf_token %} + + +
+ +{% endblock %} diff --git a/plinth/modules/calibre/templates/calibre.html b/plinth/modules/calibre/templates/calibre.html new file mode 100644 index 000000000..9e4e854a8 --- /dev/null +++ b/plinth/modules/calibre/templates/calibre.html @@ -0,0 +1,59 @@ +{% extends "app.html" %} +{% comment %} +# SPDX-License-Identifier: AGPL-3.0-or-later +{% endcomment %} + +{% load i18n %} + +{% block page_head %} + +{% endblock %} + +{% block configuration %} + {{ block.super }} + +

{% trans "Manage Libraries" %}

+ + + +
+
+ {% if not libraries %} +

{% trans 'No libraries available.' %}

+ {% else %} +
+ {% for library in libraries %} + + {% endfor %} +
+ {% endif %} +
+
+ +{% endblock %} diff --git a/plinth/modules/calibre/tests/__init__.py b/plinth/modules/calibre/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plinth/modules/calibre/tests/data/sample.txt b/plinth/modules/calibre/tests/data/sample.txt new file mode 100644 index 000000000..73709ba68 --- /dev/null +++ b/plinth/modules/calibre/tests/data/sample.txt @@ -0,0 +1 @@ +Testing diff --git a/plinth/modules/calibre/tests/test_actions.py b/plinth/modules/calibre/tests/test_actions.py new file mode 100644 index 000000000..ff61ece9c --- /dev/null +++ b/plinth/modules/calibre/tests/test_actions.py @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Test module for calibre actions. +""" + +import imp +import json +import pathlib +from unittest.mock import call, patch + +import pytest + + +def _action_file(): + """Return the path to the 'calibre' actions file.""" + current_directory = pathlib.Path(__file__).parent + return str(current_directory / '..' / '..' / '..' / '..' / 'actions' / + 'calibre') + + +actions = imp.load_source('calibre', _action_file()) + + +@pytest.fixture(name='call_action') +def fixture_call_action(tmpdir, capsys): + """Run actions with custom root path.""" + + def _call_action(args, **kwargs): + actions.LIBRARIES_PATH = pathlib.Path(str(tmpdir)) + with patch('argparse._sys.argv', ['calibre'] + args): + actions.main() + captured = capsys.readouterr() + return captured.out + + return _call_action + + +@pytest.fixture(autouse=True) +def fixture_patch(): + """Patch some underlying methods.""" + + def side_effect(*args, **_kwargs): + if args[0][0] != 'calibredb': + return + + path = pathlib.Path(args[0][2]) / 'metadata.db' + path.touch() + + with patch('subprocess.call') as subprocess_call, \ + patch('shutil.chown'): + subprocess_call.side_effect = side_effect + yield + + +def test_list_libraries(call_action): + """Test listing libraries.""" + assert json.loads(call_action(['list-libraries'])) == {'libraries': []} + call_action(['create-library', 'TestLibrary']) + expected_output = {'libraries': ['TestLibrary']} + assert json.loads(call_action(['list-libraries'])) == expected_output + + +@patch('shutil.chown') +def test_create_library(chown, call_action): + """Test creating a library.""" + call_action(['create-library', 'TestLibrary']) + library = actions.LIBRARIES_PATH / 'TestLibrary' + assert (library / 'metadata.db').exists() + assert library.stat().st_mode == 0o40755 + expected_output = {'libraries': ['TestLibrary']} + assert json.loads(call_action(['list-libraries'])) == expected_output + + chown.assert_has_calls([call(library.parent.parent, 'root', 'root')]) + + +def test_delete_library(call_action): + """Test deleting a library.""" + call_action(['create-library', 'TestLibrary']) + + call_action(['delete-library', 'TestLibrary']) + assert json.loads(call_action(['list-libraries'])) == {'libraries': []} + + with pytest.raises(FileNotFoundError): + call_action(['delete-library', 'TestLibrary']) diff --git a/plinth/modules/calibre/tests/test_functional.py b/plinth/modules/calibre/tests/test_functional.py new file mode 100644 index 000000000..9f7ca97e6 --- /dev/null +++ b/plinth/modules/calibre/tests/test_functional.py @@ -0,0 +1,180 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Functional, browser based tests for calibre app. +""" + +import pathlib +import time + +import pytest + +from plinth.tests import functional + +pytestmark = [pytest.mark.apps, pytest.mark.sso, pytest.mark.calibre] + + +@pytest.fixture(scope='module', autouse=True) +def fixture_background(session_browser): + """Login and install the app.""" + functional.login(session_browser) + functional.install(session_browser, 'calibre') + yield + functional.app_disable(session_browser, 'calibre') + + +def test_enable_disable(session_browser): + """Test enabling the app.""" + functional.app_disable(session_browser, 'calibre') + + functional.app_enable(session_browser, 'calibre') + assert functional.service_is_running(session_browser, 'calibre') + assert functional.is_available(session_browser, 'calibre') + + functional.app_disable(session_browser, 'calibre') + assert not functional.service_is_running(session_browser, 'calibre') + assert not functional.is_available(session_browser, 'calibre') + + +def test_add_delete_library(session_browser): + """Test adding/deleting a new library.""" + functional.app_enable(session_browser, 'calibre') + _delete_library(session_browser, 'FunctionalTest', True) + + _add_library(session_browser, 'FunctionalTest') + assert _is_library_available(session_browser, 'FunctionalTest') + + _delete_library(session_browser, 'FunctionalTest') + assert not _is_library_available(session_browser, 'FunctionalTest') + + +def test_add_delete_book(session_browser): + """Test adding/delete book in the library.""" + functional.app_enable(session_browser, 'calibre') + _add_library(session_browser, 'FunctionalTest') + _delete_book(session_browser, 'FunctionalTest', 'sample.txt', True) + + _add_book(session_browser, 'FunctionalTest', 'sample.txt') + assert _is_book_available(session_browser, 'FunctionalTest', 'sample.txt') + + _delete_book(session_browser, 'FunctionalTest', 'sample.txt') + assert not _is_book_available(session_browser, 'FunctionalTest', + 'sample.txt') + + +@pytest.mark.backups +def test_backup(session_browser): + """Test backing up and restoring.""" + functional.app_enable(session_browser, 'calibre') + _add_library(session_browser, 'FunctionalTest') + functional.backup_create(session_browser, 'calibre', 'test_calibre') + _delete_library(session_browser, 'FunctionalTest') + functional.backup_restore(session_browser, 'calibre', 'test_calibre') + assert _is_library_available(session_browser, 'FunctionalTest') + + +def _add_library(browser, name): + """Add a new library.""" + if _is_library_available(browser, name): + return + + browser.find_link_by_href( + '/plinth/apps/calibre/library/create/').first.click() + browser.find_by_id('id_calibre-name').fill(name) + functional.submit(browser) + + +def _delete_library(browser, name, ignore_missing=False): + """Delete a library.""" + functional.nav_to_module(browser, 'calibre') + link = browser.find_link_by_href( + f'/plinth/apps/calibre/library/{name}/delete/') + if not link: + if ignore_missing: + return + + raise ValueError('Library not found') + + link.first.click() + functional.submit(browser) + + +def _is_library_available(browser, name): + """Return whether a library is present in the list of libraries.""" + functional.nav_to_module(browser, 'calibre') + link = browser.find_link_by_href( + f'/plinth/apps/calibre/library/{name}/delete/') + return bool(link) + + +def _visit_library(browser, name): + """Open the page for the library.""" + functional.visit(browser, '/calibre/') + + # Calibre interface will be available a short time after restarting the + # service. + def _service_available(): + unavailable_xpath = '//h1[contains(text(), "Service Unavailable")]' + available = not browser.find_by_xpath(unavailable_xpath) + if not available: + time.sleep(0.5) + functional.visit(browser, '/calibre/') + + return available + + functional.eventually(_service_available) + + functional.eventually(browser.find_by_css, + args=[f'.calibre-push-button[data-lid="{name}"]']) + link = browser.find_by_css(f'.calibre-push-button[data-lid="{name}"]') + if not link: + raise ValueError('Library not found') + + link.first.click() + functional.eventually(browser.find_by_css, [f'.book-list-cover-grid']) + + +def _add_book(browser, library_name, book_name): + """Add a book to the library through Calibre interface.""" + _visit_library(browser, library_name) + add_button = browser.find_by_css(f'a[data-button-icon="plus"]') + add_button.first.click() + + functional.eventually(browser.find_by_xpath, + ['//span[contains(text(), "Add books")]']) + browser.execute_script( + '''document.querySelector('input[type="file"]').setAttribute( + 'name', 'test-book-upload');''') + + file_path = pathlib.Path(__file__).parent / f'data/{book_name}' + browser.attach_file('test-book-upload', [str(file_path)]) + functional.eventually(browser.find_by_xpath, + ['//span[contains(text(), "Added successfully")]']) + + +def _delete_book(browser, library_name, book_name, ignore_missing=False): + """Delete a book from the library through Calibre interface.""" + _visit_library(browser, library_name) + book_name = book_name.partition('.')[0] + book = browser.find_by_xpath(f'//a[contains(@title, "{book_name}")]') + if not book: + if ignore_missing: + return + + raise Exception('Book not found') + + book.first.click() + delete_button = browser.find_by_css(f'a[data-button-icon="trash"]') + delete_button.first.click() + + dialog = browser.find_by_id('modal-container').first + functional.eventually(lambda: dialog.visible) + ok_button = browser.find_by_xpath('//span[contains(text(), "OK")]') + ok_button.first.click() + + +def _is_book_available(browser, library_name, book_name): + """Return whether a book is present in Calibre interface.""" + _visit_library(browser, library_name) + book_name = book_name.partition('.')[0] + book = browser.find_by_xpath(f'//a[contains(@title, "{book_name}")]') + return bool(book) diff --git a/plinth/modules/calibre/tests/test_views.py b/plinth/modules/calibre/tests/test_views.py new file mode 100644 index 000000000..24ede010d --- /dev/null +++ b/plinth/modules/calibre/tests/test_views.py @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Tests for calibre views. +""" + +from unittest.mock import call, patch + +import pytest +from django import urls +from django.contrib.messages.storage.fallback import FallbackStorage +from django.http.response import Http404 + +from plinth import actions, module_loader +from plinth.modules.calibre import views + +# For all tests, use plinth.urls instead of urls configured for testing +pytestmark = pytest.mark.urls('plinth.urls') + + +@pytest.fixture(autouse=True, scope='module') +def fixture_calibre_urls(): + """Make sure calibre app's URLs are part of plinth.urls.""" + with patch('plinth.module_loader._modules_to_load', new=[]) as modules, \ + patch('plinth.urls.urlpatterns', new=[]): + modules.append('plinth.modules.calibre') + module_loader.include_urls() + yield + + +@pytest.fixture(autouse=True) +def calibre_patch(): + """Patch calibre methods.""" + with patch('plinth.modules.calibre.list_libraries') as list_libraries: + list_libraries.return_value = ['TestExistingLibrary'] + + yield + + +def make_request(request, view, **kwargs): + """Make request with a message storage.""" + setattr(request, 'session', 'session') + messages = FallbackStorage(request) + setattr(request, '_messages', messages) + response = view(request, **kwargs) + + return response, messages + + +@patch('plinth.modules.calibre.create_library') +def test_create_library(create_library, rf): + """Test that create library view works.""" + form_data = {'calibre-name': 'TestLibrary'} + request = rf.post(urls.reverse('calibre:create-library'), data=form_data) + view = views.CreateLibraryView.as_view() + response, messages = make_request(request, view) + + assert response.status_code == 302 + assert response.url == urls.reverse('calibre:index') + assert list(messages)[0].message == 'Library created.' + create_library.assert_has_calls([call('TestLibrary')]) + + +@patch('plinth.modules.calibre.create_library') +def test_create_library_failed(create_library, rf): + """Test that create library fails as expected.""" + create_library.side_effect = actions.ActionError('calibre', 'TestOutput', + 'TestError') + form_data = {'calibre-name': 'TestLibrary'} + request = rf.post(urls.reverse('calibre:create-library'), data=form_data) + view = views.CreateLibraryView.as_view() + response, messages = make_request(request, view) + + assert response.status_code == 302 + assert response.url == urls.reverse('calibre:index') + assert list(messages)[0].message == \ + 'An error occurred while creating the library. TestError' + + +def test_create_library_existing_library(rf): + """Test that create library errors out for an existing library name.""" + form_data = {'calibre-name': 'TestExistingLibrary'} + request = rf.post(urls.reverse('calibre:create-library'), data=form_data) + view = views.CreateLibraryView.as_view() + response, _ = make_request(request, view) + + assert response.context_data['form'].errors['name'][ + 0] == 'A library with this name already exists.' + assert response.status_code == 200 + + +def test_create_library_invalid_name(rf): + """Test that create library errors out for invalid name.""" + form_data = {'calibre-name': 'Invalid Library'} + request = rf.post(urls.reverse('calibre:create-library'), data=form_data) + view = views.CreateLibraryView.as_view() + response, _ = make_request(request, view) + + assert response.context_data['form'].errors['name'][ + 0] == 'Enter a valid value.' + assert response.status_code == 200 + + +@patch('plinth.modules.calibre.app') +def test_delete_library_confirmation_view(_app, rf): + """Test that deleting library confirmation shows correct name.""" + response, _ = make_request(rf.get(''), views.delete_library, + name='TestExistingLibrary') + assert response.status_code == 200 + assert response.context_data['name'] == 'TestExistingLibrary' + + +@patch('plinth.modules.calibre.delete_library') +@patch('plinth.modules.calibre.app') +def test_delete_library(_app, delete_library, rf): + """Test that deleting a library works.""" + response, messages = make_request(rf.post(''), views.delete_library, + name='TestExistingLibrary') + assert response.status_code == 302 + assert response.url == urls.reverse('calibre:index') + assert list(messages)[0].message == 'TestExistingLibrary deleted.' + delete_library.assert_has_calls([call('TestExistingLibrary')]) + + +@patch('plinth.modules.calibre.delete_library') +def test_delete_library_error(delete_library, rf): + """Test that deleting a library shows error when operation fails.""" + delete_library.side_effect = actions.ActionError('calibre', 'TestInput', + 'TestError') + response, messages = make_request(rf.post(''), views.delete_library, + name='TestExistingLibrary') + assert response.status_code == 302 + assert response.url == urls.reverse('calibre:index') + assert list(messages)[0].message == \ + 'Could not delete TestExistingLibrary: '\ + "('calibre', 'TestInput', 'TestError')" + + +def test_delete_library_non_existing(rf): + """Test that deleting a library shows error when operation fails.""" + with pytest.raises(Http404): + make_request(rf.post(''), views.delete_library, + name='TestNonExistingLibrary') + + with pytest.raises(Http404): + make_request(rf.get(''), views.delete_library, + name='TestNonExistingLibrary') diff --git a/plinth/modules/calibre/urls.py b/plinth/modules/calibre/urls.py new file mode 100644 index 000000000..a4cb694e3 --- /dev/null +++ b/plinth/modules/calibre/urls.py @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +URLs for the calibre module. +""" + +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^apps/calibre/$', views.CalibreAppView.as_view(), name='index'), + url(r'^apps/calibre/library/create/$', views.CreateLibraryView.as_view(), + name='create-library'), + url(r'^apps/calibre/library/(?P[a-zA-Z0-9_.-]+)/delete/$', + views.delete_library, name='delete-library'), +] diff --git a/plinth/modules/calibre/views.py b/plinth/modules/calibre/views.py new file mode 100644 index 000000000..f969de9d5 --- /dev/null +++ b/plinth/modules/calibre/views.py @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Views for the calibre module. +""" + +from django.contrib import messages +from django.contrib.messages.views import SuccessMessageMixin +from django.http import Http404 +from django.shortcuts import redirect +from django.template.response import TemplateResponse +from django.urls import reverse_lazy +from django.utils.translation import ugettext as _ +from django.views.generic.edit import FormView + +from plinth import actions, views +from plinth.modules import calibre + +from . import forms + + +class CalibreAppView(views.AppView): + """Serve configuration form.""" + app_id = 'calibre' + template_name = 'calibre.html' + + def get_context_data(self, **kwargs): + """Return additional context for rendering the template.""" + context = super().get_context_data(**kwargs) + context['libraries'] = calibre.list_libraries() + return context + + +class CreateLibraryView(SuccessMessageMixin, FormView): + """View to create an empty library.""" + form_class = forms.CreateLibraryForm + prefix = 'calibre' + template_name = 'form.html' + success_url = reverse_lazy('calibre:index') + success_message = _('Library created.') + + def form_valid(self, form): + """Create the library on valid form submission.""" + try: + calibre.create_library(form.cleaned_data['name']) + except actions.ActionError as error: + self.success_message = '' + error_text = error.args[2].split('\n')[0] + messages.error( + self.request, "{0} {1}".format( + _('An error occurred while creating the library.'), + error_text)) + + return super().form_valid(form) + + +def delete_library(request, name): + """View to delete a library.""" + if name not in calibre.list_libraries(): + raise Http404 + + if request.method == 'POST': + try: + calibre.delete_library(name) + messages.success(request, _('{name} deleted.').format(name=name)) + except actions.ActionError as error: + messages.error( + request, + _('Could not delete {name}: {error}').format( + name=name, error=error)) + return redirect(reverse_lazy('calibre:index')) + + return TemplateResponse(request, 'calibre-delete-library.html', { + 'title': calibre.app.info.name, + 'name': name + }) diff --git a/plinth/templates/form.html b/plinth/templates/form.html new file mode 100644 index 000000000..ae21f928a --- /dev/null +++ b/plinth/templates/form.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% comment %} +# SPDX-License-Identifier: AGPL-3.0-or-later +{% endcomment %} + +{% load bootstrap %} +{% load i18n %} + +{% block content %} + +

{{ title }}

+ +
+ {% csrf_token %} + + {{ form|bootstrap }} + + +
+ +{% endblock %} diff --git a/pytest.ini b/pytest.ini index e95adc2ab..cad6c2306 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,6 +4,7 @@ markers = functional apps backups bind + calibre configuration date_and_time deluge diff --git a/static/themes/default/icons/calibre.png b/static/themes/default/icons/calibre.png new file mode 100644 index 0000000000000000000000000000000000000000..12e7f1067eecf35e6d66b9de0fab02d980e6ab93 GIT binary patch literal 56951 zcmY&<1yodD^zNNufT4$O=}>Cu6ov*tKtw?rk&y0=p+Tf2r3MTPLg^ktT3R{<1gRmU z>kYsE|E;$ki&-;p@44r&&pzM&_O~~2dOGSPgbaiL0FY>EsOSR#82b?nK=H6||NO?! zv2PGhB~1e;_A3bbA{P5Ift$uNPXHk8ynTVv`A8YDzohq4HS&7wYU|}|1QII(Ex0QZp!{PUW}zyJI2O*k-NqJdfO zsiVokNLke1*7Kfq%zcLHgm5?`48#gmGCP|Kw3yzSn&$J6-fA};d?qWg`R_I9IxQ*5 zA6a}|yT>&@+P=M4(93?^yCo>M-aVPG(`wa!buxQYZ3?rcWd|UNSx>M31RD<33as}9 znOo#CS5B^}@1%1S9~Y&d=5pIa>%DviKJ4l0{!P*o*!nUtI2*bae!2P9bvC;oEQuBR zFpRz$6wWF}Qya3hS+o48g&i}Mx!RrUUHr_^+okjG9s0f@X1O%y`gQz3>hwD2djS>= zwc%qQA6yUwH|h#F?il$=BENk6X9YpUr9hFaP*tbKm%-o5J==af9c&RR@Rmrl+4(j7 zDDdxwUdZ(lqRiPIyBGA2oquGy@HZP*J7#@oUn@6tZ|iOixJmrX@&Fd;t^q;7a*}T_ zzGd?z(xKUDm68dzJBJ?6?9O`JLfZB$TduaqNV%8-VZf`IO@n_jM^A&Q5Bbc;lxG<@ z?7kO(_CAPwBn;QXB?rRcL}!gBtd;VVj5%~xW*TcaY8U;Grhl{x__awK- z6;w+k+Lr#BwsQPCTqy3lYYgcu5e=FI9-Egn&>JlIGiYp`Yz>rX- z7A%q>nF&y|hsj@*eOYk#yICr@BQ4hSe93D9(kH!Jxs{nT)2l0SiAj4Ye$;yZrs1fiiHRI~g#PH>`N!jUk#0iV3R!OzEg8T~w@sA4PJQnd0EbDWh z0_;Va7Qg4lvw;yX?w?Yiy|-Ew<6?Uc0U_Fo6(24>Zt(7!L--8Q=`6}$d9pQ|%9 zc!(@SgP_=){pGnUr!Id8(rFJ5_lTWjU2DteKY zsNst^r}qpQ9#hsj7_Xqk@L?wic&WO7n52sg;D0rI$Lc&=nPIWjU3x;@ITkM6#lP~| zfwcazg)q^7bn=s%zya+Wi+iT!qcxS@m)iox9LK-!29Jafzk`60VXj~!3Y0+mzzC>I zvNl5{DAKQ({@8#yc;`|0rofp4X-H6fi&!6m`i5`)k95_WvY5=2<)C%3;I;5!175&y zhp-8c)9Uv#605P5{{6&~pD+iWGz~dX=EN31i+%5scj6X;rWR*+*75@DOi%$t3s+=q zH~m5lb25$n=ub`oIV~QF_V=g&_zKP=M)mCqOo{_PaqY^dEqu#SZ9O*5=P@T{l@2pE*n`#oNKyd7h;%HHP$?l2 z^4}5hQg@EjW0YL15ncS1UDGGM=EibAUCKTOJz_{fyxVe+*fQsD)mJ^?-7{k7acsjJ zO0RAazk4>b|5ys4Q*KQIew&;Ip z$;IKJirM+39WSpvWks8z|B=a@1+&p#z3CnGG@DFSlvCN^Sc@K8RZ0)OP$zkd32$>&Uj`Lx}~=bHCAO{ls11*D_%qVBhR=>50V zfvnK>cgcT|xNKeT#{PIhO!3E`-YtK#zq8|U!LuNdk|uQj`fCxS?V>NSCC87BzJ^8# z7O8oA2%f*rmZ-t0mJSv4&)djypRF5pU*qZ=&%K`Z=F=Wm6^19L8)FTw`40Y?wKMCT z1Pk81q$D9`8ABO!`CKnU7pXj3!3Qmjt#7ohza&o?R+$T;Trb7>xCs(X`ZyUy@C&S$ zyaemLzg*@Mw7*CWg@?ne6HA+XV^YMpea_js&2yev^tS1~v9Y)az@p#CS^7yrYsS`U zy#^XmAmEkI^nT>Ly_(VXz?BVJNrJ^xn01XylnX|CtxwwbI5)7~%5RM+=ziX%f$?EO zXRjeG<*qpYpoK(4D4}AOw+1PawKG>+{ka~?3huIVH7Dj*Lum&8MQ?d#FZ0r;RSLIX z)|JwMln})d`rBuF>O0336Z2=?#kaW5HJ;$XtbQq-WLJwt4%Se~%;;}8>7q)x!X|2{ z8VtRvlwpxM0QD|Xc_Y8Hq(o`4ve zq}e+^WF1RY(Rv%_)ur;8E6gii>GRJwi=v8IkOML`*mE6`_xZn;7~5wZe3z=f4mG6- z&0|_6A4UyMaAK#ochxFt7LS{#!9Ge(eK7~o@I&PTSC~P3m`az5h&Ag-k>!VkQCSTE z0Q)>_&|v$+Io3tlFmT)}eRVGDADRc^YZe zR#oNnyqKFr_kJYdGkPT=fC7lPdx z-7|1nV(q!Sam@vo=l*3RXm>(DkgQ^Wvsv%xN&NW8-L0>Nt5Vw4L$HKl&4k)-7; zEv41NEQ=F4D%@#M;{z)mS6gDvW_f>6Yn9Da9wo{E&>TxObNDdye~15&``Q+-Rcq*R z@F|j9>g0HWbLR5vc?QJTSo?*DiK3AN7>lXa`**(r<%UpsV}2m#K)-^w`tSE?OJqOc44d> zAP7qB_AkJx@Uoia==!iLSyRP1{F`gWMub}gyN8YOv#GitNF)sk^x^uIL%oKISVN7+ z{x{!*Yt~_<_?Ptn`lAo)tMl^y_ex{N29mMYN~p0%U>F#Xmb@|uAA9tcJd`f}WNsfd zUSEE#B5tko+Yo8weJsF3 z-Mg83P+y*yDviNyQ5+78k(D3F*n!VVOXjy``<$grBlF`%l8hq}itLIy2U^4&_zf1 z-Q-A(%-ht{?ZksBDO(ydFtYWgczU5bV+Ovv=5)OQk4OZX0Kw|fT{*1?ukGuzIObmANPx&w6&rGS;uO@d_S3Xj4j$rL%9y6fR zNUh?{iE8RfUJa5EB>wkkTulsk$3?CO`)tJxwHu`X+cVJ7C0i9F5TD?%gFdrYf(ezL zXg{`M74QU0kVsg=Z%wW)d~S3Z`a~o8_Ts?1R478!>LodlK`ghe+T@w zcod{#n2zw^%}i)^*lKSqfR}|(2VKsv9k#Y!O`4FMU2OcB&CRcXL1dn+I zfNr;+FpK^%JMc3yjh7GDaAvvq$AWGv0B@u+Roc6^hTtJ<8J(_+hsX~HA>VN&^$4+ zw5I1}r6v}KN2Ml}tecs}q`yW)D6p)rb6fklhAo}*om4{AqmjkyecjMkY=a8BFt-Hp z=ktV1zHDjWUy-jNu*kTSno!Ea3|*%k+odt=o{kSRE*_8E98!4ngNT0pjhg`!V@0u^ zKo?QE2pXoSW>)$MN0uhZyUY#nG?F z0A5i|F-T)ON7POe%Z`+oZYLli1YJFv9ftC1-8z}{Eh1kgRXm z!AtyBzyLCTmHjs&ONW!JP|fHAE={EVKnV%&58tovVNnZ{KXVSKq(d>mpL$yo-uqC> z{ZbtXCAE6W3NFN}%1}Wl@^U=|7mj~BhXKzaI+f$~6SFDb?Fu@g-Wx!YPZS7CDM5Ik zR{+lLmsr5+9(-76NY6X=#nany5@59N^?My^H)ftT?<~afeRnICU%lDqI`aHrr=98e z{Sl&j-=;ay2EeEA`Ea)yUNx3}Q3IS*99HQ2PBj21!}*o3T5et-r?-PBwAHA~7>I>#V>$$`)knKb1spgaY&xCX;N%B9R*nc0hFnie(Z($R{ev86J^D&ux#o>)`G*7O zH2{b13l*TlRXv4ea(-+K^G4%$!^Natp;Cm2NnbbgFVW%QsR_fyUWJ7{%B81M3|%Cb z%CUMA2ZOdxd+dH`O`i3h6o_d{md1QZseB4d(O1)l!)Twg0Th~)q5teKZF_rk=O=f% zcIWZ;w>@Lr5B~8wb%*(gRoTL~4y<<_V~aCAYuxrE zBGh>SAE_f+^Yjl|?YMaz6H-q^EI9L%-ydr_cOKkZj9D8bjzOWyzUXJvgN1ngC86aWCW32r)a{-qA@iP2h~wzlua?haTChrRD#U-_tT zN&PifkeG~^Yx!(&QMYro=l=xq-=Mx;l`;XqTLfzT1$HV!bCW37uhVUSWQRbh*isv^=S> zn7HWogUhc2W195-e*Ds3K!_d9S|Jr`rG~{n@pcLjEE4{W9N4D$MUva%>}XUI@oj|P z2NOgtduK7iED6&VcHJ=?^1QIPJL;+*9s zT^~~TdIzhNI+9e#YLsElI!;GR71}r4NIMzsmQ%{3KG$ZEC@+gk zS|kkC@3<`Gf$)@NU#FxvC6g_|3$<}R8Bnl>v$MXu_d-PT-~CX)H{>KtDV*&U-_4U7 z_#WOxRHNv-r}1#n;h(kAjk`^2z`H0D>i>=Nkj@7HmB>G)iJo=-DPs=d-cD(KLRxbl zdBLfmJ8zcBnmNj!0-2FlFLMir2d1t!4(o2z$=beEnfTD9k^OqCiL(53&NINd`UguI zO2GA=V7WV3;tj)pu1}bSipUPFA8X$$!m3-ATw~x#ydxDSY@!%#qyk87 zyk57QYnJclu3WLdT+a-Bjk%niH)?SZo-nIpU=)<6UAL&9_&Xu?#se2rIV-RvMM`={ zDr_fdr)@0hehf4A^$qo?V#4me(@^=*`WBrkq;y^oa6fNo$L{Xn}wpHP2V8a@uz|RW=DF&J>;6fHIpGg>6 z-&NR5ONFdb?OrdGTh2-VXdf|`#bd3eDPPFbLFT7)q+_~T)_slnvfm$t$_;ZZ++#9TO05u@TVsKwLhizz?<6CuG0EqP3TIRnr)4L8LJr}&Q^`hGg&3bkxChI zjsC#cworE1NXoaRJCLpodzzUq!?d;m-G2f#kuv6B%dLshKZhI{9x>eK%cMS(+{;ho z{>2DTBrM`9#HF@vb3evwd%FZ2^s&&k=oRfjG@|&O~pGuzPDH2%xZ#*6qUoV}!H^#B!yN?g6&H z^RJtK(*RksBPsIYaHoo6T;$g3JVBPhaKK4zibtkp9u+YrJ^YYYis!PWVv)Cr2l?jS z0)~KG6>{LLMSX@SrzOiOXIjz9UJGFPyM0~P5N8+MfUTE>-&az;+Z%@F7&(U z)Sh=Dud0uk%GQxMJmr?WCr|D!KI0pz=PMa@e?U$g+ImoinmdWs4`bE47LIPbACwsS zki#n z`j)i(;sh$xvNAcGh|}%)J1UsrYjtCubXBS4$LPjEbE+_Lj&$v(yl4GaqIphIjKlhH zO&HLr0stYT$V~1hdn03kWTEWHrTh>StMB~{*1dDDkJpcl=e{uUXp6R4?nHf>*JX&T zB5>=N!-Fz(_Bo0>+VdhDf!%imuL#ZVYVQ_Rfh$*DNtL3~!=K|u(0`R%|3FnWIclf^ zZ`wc}Ff~|}=!v0VccDc%=CqI~cF>*&D122(;SI~bPqfCrhNC8`DXV^a4~zKbD)rBg z=q$ZqE{&LIKDJ<9l%`dQnCiy{D76cRE^*AoG_T>OEY{wCh62!6+qLDjNYj)RG z|Ke`SNj0&;7Xae`p&O(W$#~z5;A*iI$)9NllSpx~g_d4j&nF+AlrHto^DtHD*#rg8 zr2;YP>ZK!2Iv3NMd&(FCCJ(aq8!P;6%*j38R9bpgD&yZFI>228;o$^M)*!RUT&c5p zQta9ZyCW$H@AkYG+9fT~(6nd{4SD2ohtLcNDJlqkJM46;6i&UMBSQc zR{JCYCVQ2P8C2nIU&40DVulEOiBC$K>&yErX!P_k*+us65DH6_H}id*;*Z^U>e0>< zxF5f?6L>3bF(5O0)@AJv`qYyNdA8YL6u2MFG}zxISCS@4K^e9edVteOf+F~W^nwhs z{jG6J|0sUf4P#`aA`EVMe0&y;;v5lz;q68dYDS>8gO5+P!tF_HE*k^b>%O0uy14}# z+EY|(jW;wfTYT^1?cs!N_VYq?m^EX%K@X1_{`?so?iZmUaeBg-jYcLLGv>s%*+ z(OZ!bPz_$?MNhl12;VK>wA4cggW~j}V2amEH7~h&pDd(^n?jYi5tFE7Z&KgtKav-* zK}zd-s8EjS>`*`T@h6qqNO|7c7#Yz)$7IS8EA&Q%F9OFGrYO1iw1rOZv@R%$3#xw&Q1Dim|CvPykHW>W!L6JT59ArDi&uPW61nL6W%Gy|vg6GJv(` zul`VO9eXzytLj%=8sQ=MZK-C~#dG0!Ul*T(%gvIjJgRwx`H@vi#2)G)T_kBdm0-Z9 zQ)jo0SaKb>%e6!-GkxGhxa4Hj|hsLt2ZG%1N_ zz1iMQ4!_s&47c@qe>YOR5j1gJrgTLG9-jUceReq040FvsAdb`OH0Dbaml>n&Eo^PCdG=JA{pAiU#o!`R$4kbCDG)9^E=%uQ4 zo*l=*TmEsJ_n(9u7#TpfBFR2EfKr^Q^mz0c-j3)*!XGC)pQt-4b082W`Lgn25msu7 zu?pBk<^@n$?<;4eXBCTL9e_DnoK&*`Jhu{!p1*hl|81uJ0Gq#xiBd!_vakd@2G5w> zKscdr-drYLs3=aoGAQaEEX4~4`tTukpMYdVsO?&Il-8&Lyy>}G5h#*AYM7;(AOX<4 zgB)J%w539d%sEIC`_7D+4j$O5@Rbdy8Q2Y&~Eo+%Oz#5j5ILCKp+&R1M0E$blWBz zL+R>4ODMkb%ot8Dc(8ZEN#5xbz{tz$9B>xSt_?f*?#D**0CeV52o)q;89vODWateY zr~F0`4!ipe20#%0xB*qRmluY+ZPsM>@sUrm7=R4( z^jSCwt0~!mMV7?I0G4Pm5~;+lv80_SYgst&>Luf_mP}&Tf*qOV-ayI485M59rxb-u zSZF|P(0!sMjd33_h_s{XI5$+u02}4uUz-w8VUME%nB%yP=pn+(}2-6%yMpvq=3stPz^fA-F}gP zY0j5j!~8`Z2UZ`(E5ePW5A!aNGy64?{4y-+4;R){S!Ri?OvDJ$@J&#ki3$WN3ZguE z9Pv6Ujv`g?c1;ODR_Uog$WP=>GsT<=0%mC-Z2=p^i}<8#Sdw)&(h_^34zYSEsXZPO zp3@EiP~KbK6pLJiU^k_56DPeq>n;_x+G%h6TbR%?gQDvC5e%roV=|j#W`&OZO!-%Y z1AHynu?J)d&POiRRh%PPRl=Z1`gZ4O%BNqABYm4N)OG**sx4J3n(dK7q?ZIZxK=XL z-)+8-EFrM=21oau+)DTV4N{@UD{HdFF+VXev>f~MgtY@|e9cb|SVgma!quaErO^P2 z{O}It32=K9KJxw&lh=r%ny{!~`tY)_^gc;UFE#M{GD-#=d0pac57meRU^%JLlt4=a zN%=*&)kjS03FY=C8}!x1B|$vx&$d3#Mk`(=6B7o8U3vFwB{R!^y>w%n6`WwghYvcS z$Rc^qI|^kjxqu!7M%%YL_~OiLQLx+O&C8{8MhT8|5fl{)J1G*pKN?HPM38{X_fJy* zX}pwQA^?lzjco?@LR-Z$`4?(NRb&>woqL~9*LUnt)f14lz_WBN+QU01kT~u2Uuc&b z3|*DjQRtkJRLus}&>(V(SK6^K?AgM-C1wP1*kGXf6&E4(?vB)?9^Wi2&Nj<-Qn|V! zKxac^8BhdTW>-iw+~~HoY-<+`I?!D}Ce~EhQ7KnOPng1ZlJ{@g2b5O-iE4fN^cfbA zt*A08%OghnjTu*O)l}T_C2M)u*T+q(m#U5BXT!%uD@~4&j=c}bvFo+R!QHOk98Jt| z;xh(23T}=}DuT}BeDM$%2p-i0axNb$+w9XQfy+^6%7nr(M8=01;uCjZqo#AT);T0M^x{8oDG9j~#`L46L zv`4>PuWx*pnx4cLQuzx6;YA2%{EVoBMeT6_6u2;3!dg`=AvI@p4$IZ|j0k)A=hL`L zCx2zldsql4JsXX`{Vlqa8+uK_|4f2*$NA+ueQverLpmmgjuGpKsroHge!w|EdPgpy zX$?I=d~kjfu?xQWZT?~$;jB|vCEGrN>;9bc;hfjln%_yIMRd&s6~e&l;lyhW^+6;W z9t^@3pHQSQFP4Z26=(IieQw+UUaX#u%J0+xB9(7m@bW*CAuA8Xj-KwHEG!iu88gsp ziSTZmY+j*v4Y>MI;f$Z8P|vd1eC2-mJ8WH8CY9(OhD~mn74(i!r%wO5<;&E9f>6ts zXi&vhJCOWkis3TXTf=tkI<-fdo8pySZ8#sT5XyHU><9`VDm$9(2ht%TfF0YpwyCoX z0hVM;MAOmzWz$ICruBOR1(T$rR%q|CwW0mTd8wK>hUP64nWjxlNpi>R*T)!4*Fww6 zFlh%KknmeYZmmU#S#4vMT<3s@;!3QLf_q^v`lsJepn0Nno`Ct6&?ff*;#r@))sOcdmIbn%#2K3vhS6CdFF7do)B@wJEB5~TVAIA6&vsQ z%|&Qi4;xK}Cvx&VUgtJtoL&0woDSSHlk;T`5#@UO1(>&{vy3zHx?hvt{W@F6RB#hh zyApwEr7@#czQ)B24O9r=FJ9JKJUUJi+x5cUZak``cC_ZzbjoPp*%*sIL_|U4gq-AmEj=?EoC_8x}ar^tsqu=7? z#!0iGdz=R}jcLdBX!g3^u+``rXCKEii@={UO_*Rq>-}~eI@`MPQ1IKVlzy4hu1QSy z&g>mLCj1Zo&WJ`v$tYxrYcrdLQ$C?jEZa6QJ8PEnkq4Oj4;fM z3=i}qJfou@45Jzz%~6p_i)6>OfxXd3J(t_%daQ}#>VIfcck^**X%!){u@!??rNFOC zp7Av+hAd>y*wlU6fipEd?XtZl3{*<5o|NCF5PYp8cfEJWYWwntC`B2e5D%0Ph6REy zZ)>9{^GJB}&o$v>^y-DkG9jq^s`ciL068WkUlqXjy*kwlImcM^HZ8oAorhl$IH`Df8kqEar@_Xr!_^{; zq0;{wWAna>R`U}XJY)b;l&^kf-iwYax`chfc zAKlpw@?goTmI}SC7nXq7vD{jWT9_P&O#ed5ks=l@A7mSCBVD(ipLhDZ^n_hjju7W{ zWX@FmcUvj9Vm#gZ(=qG5ESo+%=5uaB$nzR83k2O0iam?2HU*`X-b`H_WO6a6wnrx!C|q+Qg{HdFkc3>D zm0j8Vc9!df$-Ri#J8FV&6&$-QOhq(cn@}xwOM(AMg_QXl04U;KI)Jt$VUia^HV?7j zi0V%7$I#OcuIyW2euz(603Ez66eR6eq$^R)>ifThuBc=SX2_1)uk3bCwVGR0NF!yG zUY%#TYhnonbE-5blKYkt>k;<8+e*$BKb8-r%GjwRJ>b8%>PJ0WIa?>i<|R%@tJkhE zqM}tDbPRpVF|`V>FwTK~Gv6<+dZRC2#(ezZs0?!m;{xkZGW?hC=t*L|NS|I5dRS@O zH0;Y{d{`BzKJl9NA~CEQPcIfLlUz5gEw$n2cTe7|*-3?*iWU0zX@-PsDP*~ocy%IU z0n{DXQ4*Ol!TV$?oE-7XeF<|HfEMlsg3d$eh?`1C=8gWf#AnK_sC>JO&Uc>@8K=| z5>1|SLfwHsX3;#>^v3p97w>-yU7s*u*s5;l1{mAw0GGd8(#t#xq2)}Mzit+~6nwRV z>ZYR*Sf}-=$_!d>dHwoN(^Khi=dpBzq)bjkDKn>4ohL|&flR)Fw&3DzqxwuVf}9U59qTrC`9aB_)U!R*^#R z7Wg`j%o!jPr!+5PlH2>_d^sEHE_!9z<&-Fm%_kFgAm$m+p2TNim6O-MkD#Z)8+JsZ z$;`dQTzQbJ{ed!$(}9A;u(mc~ziK47d#6=lD-dK<^Zbrr!warEfle$wyYhtSJYR$w z4)lmKeL^U4jX0t1^<~^$;@rxsGT$nfhHFNSN3){?So7~f+(6_Ksk1qYu^3AnBwYDQ1bY^il)*0-uig=kwjU zr`SZ1EVPL^qT|_yUP=BGHM7_c9&LKU^57LMw;k^-_wDgKzw`Ry-GK;fmt=`hvq?+- zy!X0*I?$7Q@&uljK@Iq*I;)GijY&v6r8lyIPl#HJBE5;f;#bfdk$(};KDC2efO0m4 zLRQF$K!xJi^4H~^eHDG&K^B02M`qw}PmV9ul0_6~d)kw!W+mW-t}hkW=0n`a&%_GimK39!u-gzxHT*?|vvy1|+2-x40GO!c@xLn)Eo!#&tu#m4Ql z#UEUu1acIm^hdV|HE`fhBp{#(7uEj@0U)Iw+~qd%Bw$uWq}6#aih*bFb-Tx$qht9R z26#L)$Ybd|1jg2`@xYeSE}Y6KRo7AWdI$v^LoAZarN|*a?Y9WIKfjjCEuY@`h;K~^ zr9~^&va}_bC~LfI!ea-D!sxMuOJKL6aWK(qco7qi{QJ3f2+uN$kTu!|nIb%!AXYIe zS>C)G{b@mqJ~G~76i2nJyOxPLh*U8a{B(oz=ovFfHcj2rg{$dq+J8IOfBDpP?=)Wj zTP}V}TRNAVEvqgx%0Pb-`;vcEB*K1aw8S|!JPQh_$=l&qZvK2b7HuE{a{gO%?GawU zqxV;4t#)geGzQumxxAnS$|#*@i$!%(D2(hm?f%G58{*V9^*v&sdsSF1d;eRsEbK{0 zWE8Juq0qI$j}hg<{Md)F%#zOY*>X)6jgv;oIIYrdzdLOrcoNG-Wv0(MOcc_F=MuIx zBoSM}GC{I}p&b|;01DBMmK~7&ql6sJeIjCw4O<8wF-(B2YTZHK6MIY_dct&@kB322 zGVC!)K~d~*j(a~TBi=FxOA!56Q+KjAp;Cr*hHMdq6>V^mD!(+M>K7D7%^va%wAF!< zzFI`_i?l_ZmYZV-R1->%)dBPU1(P8bxrLk0%hn%jiAi%8-Z0+Dx*01Ek}LKHt*O(5 z?Njht<05*OnAZGL=VGHSr;;0g!mYA2c{xBfSuXnCL8|C*-AhTKt1Zcquo99_Jo_+! zc-2D!n5-`|zt7U#eAve5ImoT?d}``!cP5U*lh{1o_lI_>X5@H-EY!uQo@=;? zG}>Jd@o9-kgy))|(t|!%riwB9*dI>ulJjvX{ILKld-AK8#!rx!PIy?KzlJ;Y<3*ri z5z*nFwSH27>5tA7y{c#yg;e^MS4XdiJn@}7|11VO{SErv;_GczwYFtfB=I6gqqKhe zPbN5Sk%6ABj$bmvtjp*7;rKU$&8apqCr5%e{`X^LA}D*L8t=W&sHVa%2!zHPnuV)0 zkPI-qBbEdgzIIReCS?V@1qEUI&^9%lLZD8}`v@2?HY06e5HX6aW0haAtc2pU^V{}k zlpbSMWNDy~$HC;cj>o_T?w-oHk9J1sO&jwx=irmbBVwn8{diY1-$lumisRu0t{3Z# zr~z}BH&vcB=LgL7K(o~4rS*ns1^8umv=w)XMh$xWrI4b-`L6i9{nGCe=MqV*gt_d2 zt|ZUC(v8J2XSdY7jhCuajv&2HNF7qC0wwVQn(_uZwF9u!v8&FP;_^t5r%S8L7&b`> zs10ZGKvl=L!{M2`V&Tsph;oCwlmIx6gUmflXI3PbI2Ay`1R(!FQ`n}w{TOVH$-0!b zxGJ5C4#1frj`IHc?M`HT;fTI~i;VAx16>!nl#iJ zTQFL=S)yd?Uoi> z7Y&9=-CVkS-N++5en7;fjT=FzOmBVZipcswhj;Q)K-YpqJ06&7NJz6LTxa+}Io||9 zw--IVBKFW3GG?0h9cFH{z|S(YIvuz28o;)A@95DYE;)2_-tv*dX~4*(^z|r{*Ebi7 zi;1CEGZDi%Ruq;2SL=NOLx%e-+VAycDHVLv%8%D~`D#T^c@ng>C~i)LFr7_j&HxTU z8W>bqk__Wb5Jt7QSzud*A(7^`utz()XOxS-UD7*c)-0PRKQHFuOz`>;A=Q42iodN` zSx*9$Bmt~nJJI{H-|W+IcBXcJs{+pppc+$5)g(up*jVGn0SplD*v|!?i1yeC)V|jS z-bhlrjjKIV?hx)=T_{CNykwbL;2oS!6B@SGHQh9(gwqPBeD zGk3Azi42uCb~*WY^VU#`VUm3LxxEu)84NHFo0mU-&Cp2n)D)?5fM1b!KBr6HFC4VV zX|xp%xH;7l0Zb<<=-njO71e^q>_io3jr%3tLH*)ZbO2f^=v0h!M;7-nh}&P_rX)d@ zF>C*{_{y?Q9II`!k-xI-i3QjQ=V%0)v1xGv(!)UaZ;irqm!rHtMOoYr8ZOa}Js)J2Zrf ztTX<6B!Z)h6@u!&;sS8uS|T)NG?^TvWB|u@xMGVKqdzLa*BH#+5$Q5H*qk!`p&br6 z+b{hXc7Tnk-z%?mClQaAnE+;I1VIE_*}6{1C8)Ff#m|}#@euoPctYQA>}T7JmE{y6 zMS3@(IH7{5Z~G1)=o4rX@V+^fbv?qi_*8;Pou8%{%khP=^i=|7sw#XJOj{)B&CcWi3+pX0W2#VKrddcnE}Y4 z!TcvW2iFC1@dfi2PIED>gbyL2=kG2)aR&fcu=#xlR+coCQ5lUe@>t-O^T%8Pn;d|~ z(bB*^&v3B$N&*C^sCq^LvL3+&fN5c(X<evyCZ!ALIa4o|2jej@+|734X5%2^-dfofk`> z$l=M`xI^I>Y~9Vw%<@jztUz|AmDdv8)VKTOmiWq`l?B{k{x~E%@BnIjl5g$X^kZ~| z)&!>}^tB)jPB_di)`l0Gf7NA@-1c`pj=~yjm)qXXVt%)`i6P=6S>rqq!ii=fqzlam zbzl^Vor%r4!cJguE-QA zz1oYr*5qn<_~`8+0?UK~O4cqhWp+#cO~9bTcM#F`36E6%k~nH}iw_)2M18`2ubq;H zI>ac{G+y3INNw48^$EYhjDXp8=J}BY`FC~LZ!k{qfyM*^MK^4Xz#+ruRQlj7NYP6^ z*sb5D0qjIJf`0E@f)M-LLpWHy9(vO4=oh@>#w?l{i>#kYE1G)L9NA~R ztXH-F&TH`a@uu`P%tyn1zRk4ySx*En=SxUT6hokw^zOtY5-&&5>aKj_A+s7dynM^30$j==r!v)T{ssM0@V{PcN!L+v?$ahW0qG4&$B1Ggmi>AE zRbDKIT0pYQRlek=M*u@bM2a^WNgoI`uz3*|Dnlns)(?Rt(7Hj675Mr*W<>-Wka%^?uFowDMPZu+n*Ur=PTrJc5j-J zn-pENf_qa1FWc11yBZE@+r^+!@WoEDkD<9D(Eg07l_7a=YQdQbXz0Zcd|?@fdV(n8 zWYUMAf!!~wI8b*a;)>W5OW@pjvS_mQ=#T;<#-idh{3c|g{?Ml_@+TbPEYr)&O6-Ns z^`bb@SmdvBRsDGoj==VxwCO1T0y{5FZ$2OH&oE9{@6HixcQ^-M3~4z!f)CfLE!Pfo znzmn2*R3(ruB3F6{ocB%X4c)2iw;_FHF5Pg3Ftgxx-C z!$(*u0gh{fyFf#O{?xglLdUS%0ySt)t*5W2<^T2qK>gz4@M5-Mkx(nuV2aewiN|VS z*q)JcsELZkhuLI}KU>@YfUbF6NP}GQ#gXz+RssAnh>%m>pi|V@kNLdQRThf~-{!05 z{N_{9UVSRS_BEKaSU8J zc1jHbv1SWD8nR&BscP>_pqT{QCrg8Xe=hS~p2ZT+Kg6a3mH?||oAl0Uhe89xTOs3c zubD`F;K@E3;*)kmh5B+o%D35u-zv6dyTCF%xbWR+A{dN zOYIBuIP4kk9wOXGg2YO`Dfsair~3f_Ah^@ejSJp)Cvt0n51+W1GkCzU(7$iu3@vII zSVAyd_WfDAdiU!{rGBM$z72Kc5P7-swf&lMu^{wZAc}-^mxOe8hop4p+5XRauJ;=sxM1&j_Ve8L zTI+Z35k_E#z6*l^jM#L~!WA{}N|~nyA#@m&;)FvFu}TT`&L{t7_I>AdJ4zP08RQNF z1YzYh3?@Y|&?GhRURN^knRa1}*R)F^h7=~WMNVeD-zm}k=r+P7W}1wx59U?Mi5_0< zH(`V3R;5-pKbA%BR$9t3+UkC^&WI=WE?%djb9wDhiDWa`!`z`^->@fklu1Ld8RIVl zx73?@&@zj{6*`u#W&Caewi53AA)+4^`xs<1a7Bv85r2v#sYN||6Z zy1v5e{?c!2n?g*;&xLif;LF__KTBYGpJU318){dwC(Cb2!nATZU?3jR+Dh%Q_c-{W zIR5IdLsMJ=D?Z`Z^cjlTjglY547qgb9SDUDO5r#IwglVg7sAAl{6eSp(~ae-S4^Ka&lxj2Abv(8n#TeU>^abi)ZDRpqMCR+Me;@V-p zQjZ4PpdUl($YwhAh~{x!10qpjD3YzM;{i|WO2@hCFS=1 z&Cf`LExooFX!`E6kc$}R({{0j(sM?P!3a|eMgr6@nDtm|V|>@@m$#Ou{1^hU^fCir zqv~Gc?i(vzo=?J+x%cGwUvyttTY&=!*8#)tdHfFPEilV@KbmUTKg-2Od$~EDEKuE%7KYFnnUDG;Y3Oy8LOfKbzKTSw030Vm zKH*g~Hk&k{()@H%;IPU9Y2efg&+ok1dnM|9z?(2^m~_pjWqxsDTH($G&I!V?p+cSQ z8>Fy0(DBGm(_w_Lzjazjbsd_CJ6YMF9-CbZbV39kSxdEUG#@DM`dsmL${m#+;d6pW zzO7Cbm=1MsJdB9cox1Jg^IOlNZOOX(o#C}tyXr(eIiH%}CZzqN?W%I4MSbn`-0sby zJIVdQAc48t7`7GSWP>CbH>%z1YODQXvgApK!35OT5c6H(K^#tfte|0R8|yO&<=XTE zD*-(ayn%X`Lwy9@7_!BQmSlLFb*g>&wagAKhGk@t_P(P~5jocn*0$2wp!2}MX*h#r zNv3qjFMv-&(Pn+OZS8gR`)T*a8({bGCYIhtAM(k*$^HWI#hmdSOij`om>H&nYH{wj zwT0SCM@b8BEQK5U;y!wGnQc(gHofr~G>qR8oUA^OTEyvc7BCl(7)?Sx`+u1BRVLLRZ0tTbT*EB?O&Z*(|-6Cz}JbgyEg%~=I8u%+lh6n+K>E4QK zPQ)Yk21^XB32Y_k^*kW!4LHAZ-lKI~z3R&`kpCC5Abt9^7Cs(`I8}FET4hNkD_wFsF-t=NU>u_7k;YE|j$1}YqzRsz{fG11w zysb*Km?Jg}VP11`*$Sx9)PfU_UjFjm--e>bxFv8*M9qBjL-NVc$6A{5 zF0BfegZ`op4)W>GG58nOkU(7P`S(=3`%0-J#rOSbY8xJk1}sWvsV?5M6YRDBHg7jN z%b2~)9JIUH&hZ~g#fc5IC4yE>cpYf-v{kwB9;$hI`sVne9X@D~aV@p%)uKY)nziDo zj3}J|4h(eUvyX(WUnA&8Q)MJ8v;LrW<7eT&gVudS;SoUqlfE{ef6^78&p5+ms_*=hDY~NYq|3#j^zn+n8fRV^ z);Rt3)@d*a06|lF9dMFXMpGJyd3^(K30|k#7H2_~QMVT~!~*n$OZez-J~$6=_^({g z)u3nhAaEci`^E<8h8*RB}tve>1I;ch{1_nGjy03?a=B_Tf3;dP8^rr1GeBVtYNtdv4A%4tLbz7_dWl>m+qe-X0|A#C#g=e>o#Kd+xGP*2Cy~7d+sQyaSrpx9a0Y>BKGI|18<+{>a5-Kb%EsoPq zwCawhzvPiqC{C?rle=~*Jd8hU!g**u;-$pLPc5&AU~OB@-ao;d{rM=nwrN2dFnBwW zXon>s(*r%&ylsO;pSFv}<< zoS2+^kYbY^zx%~n@p}LAO}<7%ROer8|CUW1YUOVqXKrj)ySn3zMfz-0lFpj3>@w3rYPK#?)gwAMzu0>zs80Pp zkLFpsEqId;n@{e3^MQqv(|vsK-e7n4=UYk11M_A*9pC*$16Zdk*L%!v@uR*H?JsxL z)a+1UM;X11pTe0hzf)}6SNGpNS2Q801(0O|lMI~9;GL70#oP z4G1^2aQSb)l9~GMF2;A+RcwJP<{hpG#}I;@714^UTiT;a>&~WHt&hy@N$p!HiK?*N~1?diQ-% z%e$7$tjwt+{snLtVh=&c5MoiW8T(zWH4J@}sx7SGM%5LYn{kk1$^Wj&aBdPuawNp6pH@Fu-fW(rw;c;3XnqoMTre)<&T1x zi;--E73!3wAVqyE0l~)1*oB2&G)QASR`z>_UKYgAzeQLn3wa1?MX!hhrOtyVRWi zPV}ENA5pe{&NM3JVzRF#Yr#=S(ZYozhH_2Riq~{;Wrl$=PC1m0RZHeg1}9)UqlL0y zM$PWzU0}BmA!w6Wk(212i_?8V;&!F!SYC09eu=(M(@YBtEfmzXKViz4l2bMxa_C>o zu}misuh$dz_P<=!4UH^k|`AWO%%PM?UJO^^O&;p*#@l^r3TxQfq92 zE`%;oVQ5wgIoJY7`lFrTO5C(&tErZnHLiV!_PC2#GuNY}TGAye-9vud+@VKP8wd&Nake$ahl|^+d z5FZ_FOiN2$Uw1hfW#Z(Oymtrjxw_ukqB3!^u+Tcr(FptZ0dGY5PBjH^VMae3?5Xs| z%Cl*l`Jq9|6yc+0cvO=m0k!>&jt>A=YcLfta4+XJ5I@ z+H=;smk!`>+aFOE;L*c6&@W#mBx=0SlaaN>id7#H)_3DF6wuXJ=yuV#NS{{l2+q2hq&(bmq43d-NZJh}%J7g2cNHsKGDj>7&$R^w) zM{*gIaBz5LA7I03JbIY}$`(g$=fb12@j4O>!k0Y+^F)?e0h38Ib+4BYM6f~tThNli z1POr9u}uL+lswVARxg_k^w8?%AVU=vVGBp;G;iM_Cy|_s$_6gr!je-{kPu!k0bC{L%WGm8a9>i3+6Ngc-5F!y`}ofj zMPQKk=F02q5f(k`W5Y^E=AlD<=!s2gU&7p?q1i0WPSN*q`Lcv#tq6}T)1<|jOldaR z`z6Hf+o$P|t*eE`1tV92{d&T}*<2HXSAwB}s>(mO6EvuO_ba3>)ucylPP?I;1#mtl zg!<6R)}SgJyN7+VSr9c8_LKjM8TzB!htH0bHOWvG)F>8y2rTVxQ2yoG&N+_S2I;>W z77C)9PM0TC>+W^Ed!-k0A^3qpFgD9w0Vb}R~T}bsWYv~>C}~yaZgtAh|5JGY{A)hX*4EK=tBWIyukyGy>)s*4kjST# zz(-o#WTp^=LA5>3BXe_V?{h}*%&jH+J$_LM@H76Pq-$BYmc3b)AAccXK$ln15h2v6?8I8b2*M(&#Y(M|Y0JEK2sJk?@J6VdwVZYKoz98soo6~byHrpATH>!NJ?i*8ikYlMw%aD&Itm>ElnmICP?gIjU=#y z!k2GF0nZa?OEK`_Cn@R{liNg>j5Smp`-_ZXs0KDuinnHX1K+Xm0}ZybY6F!|EW(08 z^lxNa6xe~ire?#ARMN<~3Rm%W#e+Ky1DP~Y8ISuT#j9g!BRON;deI&wie}hRNXX33 zM*Fdgt_$twC%~myHIQ`4dDDxnDw~D*NG&}@lR*opm(H^C3MD#n6dhYuUtdbn@H2Uv z(WURWK`|rs6E8yL8TPk1Sh#r5nYEpxmCE(twqQRkp^4*Rf`(`+PX}xJQEadlT1Dc%d6u#MCNlk-Hx@5xJ;<2G|#KT2IY+8(B^8AqKyO>(j z1Hi3HV{*LU$TmoZf{N4v>BZh$m^{H6XS(IN5Co%2eDQ)2_vzX2`kTXNW+SzU3A^G)WS)yI#Nxt5$I6hw z=N#&(cJy;m#f6_*3fJz@;vVryQzyTsIKYrm*O*r@wkD9_80?8r`e9L1lA$2&{f#60 zM1vWC5uv}qz+cmg3H|pGv6DxqP)m$Ose6n!Fzz)I(x|P07-S!ilAHYyBO42T2 z(|#sHFK=4cfNf&7chaiVa1E}^Nz6Lo{XlYpMo(OBn;s+%TtlbY7w<2Wwi2qw}?^jNaj^rFpCu|6|tg|VkUbxu$ zUf3^ixpR=@D$hY=}iHY^+ z3zz8jix=oqP#*fp56a5gRtzNC?yS;rtD%o&cl4_9Jwh-e5>>ErbT#@y-o;IC&HN?F-j1&`}Vimdp9@?oktz7aeW*wRbmWnm~RbI)Z#zfinw+=ni@Y-eyQH z#^SPF?StxPN!E?GnwRy}^mhLb8~cJ$yIu-#;U{aQ`gVDBQv z%Jj9TO-$HK?bPIH>}i%d6udup+jV)isx~w|S{_2=8*jq9@GdF0)J1+hh*pY3HPCb) z&5XLE%mAx`JYn*OoZequDz&n(oThLONubvO*Wv-}XnOF@CL`M(L!Dwe>M3cw4tygYy^)&xkZ*5&hGtsXxP`?!;G;#iD+jCk;v z$GNI8LDRrIM7!=rj+Qs zxb0SLuaq$A>ecxv;K^FJ-H)_@MQEu4Ba0G;y*@W_o>`B3P?o)rn$c^q5N6oE2=GhL89To#hCDS<>FjA z`A=f8ZJ5ccN!X#&Z>!xV%dA!o8oYEekHUXSB3LH%&v?34Q083#BG`~>Qd3_ymg%G9ha*x{A3{~r`10!33^&x% zP8CO*k>{DPJ9!~kqxM{bfz{`9+3z_4uWG&+yAi-0Pmo^W*C$Ec#}srj!G8n?e;c{< zti%{BD=QerzsyEU{i!)3slL*0Mm;Kkv~rIAl^4?$naUeH z*;`3nqTJ|8#a3TSGJ=2BB~T&7#(~CQO~QkHSFSEU=SXO&tGtj)f_BKw7DDX%{{C0KdR5#bHn61gxj59#6zM zP5$M$%u_2ecXut@Sf!W?(Fps@qIOs!6%d!zc!yy{uK7HYwe2)>ckBh?H93p z#4ilUg9oUeQz#UHPw}f$bpjBkn_x&X2Kku7iVdmlLEZAH4#>}Jei>gjWMb&b z$JAkJ=8S{GiyLG+qZcWXpy$X(6MZ~TsI?$w*;R^;22lzRXZ;|Dg#m@3VLUQlbvr#B z7U4}cw>4Db0MA^OHZvwo5>z8C*=GW&Zwu(cf8@S#JOyp7ZvxS|Y1$Ej?!HGN#PLc{ z->)plpEM}&^MYxzyBU7Am8G}LGp)lQRN9M9lhN z4p-cjZyCcqGVS~&F*Y_4R%qUrhf|y7bXCQM?LF@v4^(O{8(==hvIuV|36!6&%WTR8F~-@-vEH77uNEY^j{?hbn<1` zWO=g>%Y4%))D*alD6Ivph(Mc!@yRS+&e;0vTwv`Ddkbzot|7R50-d-j=b6xYNB@AA z*4OzO!aummMjw1?`zhh}s6NKL;CZ?0CVx!sI%3j*o7h`4zY zjB!~XjR}qe5KbFs*^pBx0=_-rxN&gI1D$>WgjB`d6+ap)s+Ax{vaMsG0j2i)$30WN zX$LC64{;K}^4z!U=J>U2WB-@Hc&FciL5|Mq_@Kn}uAOYqBU$S-0npR22_-K}*EN1h}f zAOE~1Y;~my>}8H06udv6h@f`9*|%GYlG#6<1zHb0d5Nbf)JAtGw*J;jAk?Q5O`}Ov zK6YN~5K#A5(xt3mqaz`S=CswCm2}i2qh~%8_1Qr$dzu0p1H{DErV2-QXDWhLf^Wv! z=`I!E=+OVQK4LZM-pR{v?e6837rc+!qj~J>LlNEK!q`~NtMX$U`{E(7K=yClYLzR? z{YGP4shh8lAw`3+1~~GdE4DLq+El~_WXCI@R2*@3TZ55GKXHs9$xA8#9*@{9&pzBAjRCl( zN#!x|MyItJ@8r8ghdtgA+gk%e?ioEJME`2o_o6Sw--2xzU3C2vH0XyyvOxp(+fV-D z>7O6Zin#;INCUb0_f)0TJoL&ohUC>m_h$+cu+mHOa&6O_Yj4|K+{9Gk( zf_Ka_Y-Rfu@hKpoMMT~jr6!qsBESD2^B}910V&HC$}B;=CFU4{fZ+xev(+cZ9>_?9 zi8y3gdEZ>H@YH)WOk=tR{n0^nvaZtW(GyBJObm7QY2udn6aXHX(Nbp68;b=#JA(9x zDce8|b~8R(%z=+sgDq1!W)oBeH`8mw8%#To%gZK_i}C2dgxQkW_UOz&)6UgS zbG`+!`JTfGVTZZe48t_x7zIYJ^`SJ~gbA*0;Uq7t`F`9E)UII#>xGJNhptL6VMf{T zZY0Z(QbTN=nL-ph7h*^N6y2}!c2kY3^C3!vYB~~^ePVjE7!*_j6tp4~v;q{aRfow! zt7K`W%D>k8(!`FfbYFL>_H2nz<8*FSNuT}QZ&8^lql2iL*kp?v;>FVtEKy&77PBaZg0{b5KvqAyAtoDrTvjGV(Z|*Bp4l@@89dEvi(|I z2i zp0iK({U~3iq<2?+n)9KHYr(Ic>dQ}-zBP-jvX3J_NgN;kZLN5-_{nMt8`)B3<8_$o z^828h6|_X(=*{D9`-rZPVjIivj?&&*N?92#(5A2{09?{a@_yUDwW>16W z_YDdb1ALz}8eG4T(!*$L?q+nsFyiN*OhK$2tt^^Xs%o0bf%XA-Dl;~C`k-=J$={Xs-zkdJPtr6d=6PLv z5gaCe%)t0MB&4Afsa?XM4H_D^^3h_S82%Ed*#sZOnf>LRn1L@;wW&ku#fdC# z%cb&LGGOi(C%k`8oGu~R^5>MvM&@aI=7p(?oq1ND=*5}%?p#e~aDz?H15szkslbq@ z^jbo~rQgcv-Hjw-TjJsrSm9HNzfL*QL^X_+cP_)rqr@a0|A}~{Gp;^(88sl=$Xxw7 z;SH(Gn}JGdhb~*3n-N@beRbXKZ{^(yB5&H@cjzCzJUWT>#8%s%f(O@gYh<(~lr_?@ zrFmU1s@5qIjDVQhA7Mo`j7L^5AFT@!s3j!kiMo7A3n?5DG{wC4zDadaY!kffXhQSo z{D6A8Prb2JwwnGVFsMw2^OdpbHW@a?hj&jI?YVyk5LfSJifeTR5M0l)_hi-X9=OOJ z+d(Wskb?a+Y9nz%(qzQ+a9M0PtX%L~JQ=IM3SiWP^jkd6_T&VN3gMgBILNjHzm01a zKDWrK^0zHV$#h)Ghkb(sM@Kt7vg$Xu)_wX*H8HHN*>QZ0AEVIwf}Mjq zzo{8d zxP}l2f(9S z8K<4Rp?FwM{_yzOD}}*>x$oCrOT@IM?&fIw>6tp&XD8>Qql;37YDjH+OdBY}R!t zGzvfAo_iv=iWJ<@6Sh)-g$oiK^BrBh^zKT!@?~sv;0;rY@=nB;B7uI|^4h`%=li;( z;-)9~O}F?We`f{`C#on|4wr}dqOu%TFSe@2?3UF(pmGEx9w){smVGN4yKDMWO2VoA zh3H3WK|qR_On(t@bGY$ru|4{mR<>9;5f}Dv6_n=t*`e}$pvbCp2d`&GID14H>RZSH z(7Abl+Pg)F4=|E3RlCs$5^A?nDve8XEqY13aTz_vvD$(5UUYUr+S~cCVSp;S0~GN6 zUi>ATT<8UAC=zFVbTk#aTpro&U4EBtaf}od@iA?l<{f>$^$Hyc@7WQ_rYg7Y<=l&r{Uz6_AqjcO zPDS@asPw@Bk}liM05GDTKXJ@c-TaN+@%whm>dvA=3Qrsj={d$dKfP5C`y($(V&-lx zDT|KINQBjC#s@Z@wL{aDoAD>#-!H6f{ywkqp2V-1rj+M&oI#Bk;4FI(eQZ%$I&r%e z?2uj`rmZ7{6T|f_H1Aa{p$c8lrI)a0Z2K@MwnuIP5wVUiBkL_yvMh*x^vpEgfFUH651$Us#n(UuCl8~Hz_(QMV+#KmjYk+kVw8xrl!I)^ zmAc^W58PalQP=((XO+X~+S{5rq$zaUx`!4+h881ut>%KM;A^a;>-586^9i`yYWC00 zr{kNCWf`S>>0zn)FEn2a;E7iqtzSzGIQm`18u9l=l&dfIiT|4gUgD)g`hX{!crtU)+r%Rl(r1$gK3*l3iQJ zgBf*V#f%!IyofdEG2CKhVemO=S5O0Z*`H!E7|E zX@lFy>vQPF`NQrlAur&Ws*Yya*JZZK zP>%J&V37JSsf|%!4Y-IG@VnTPQ&MVUG;bTh>%;yaCzu;c_sOz#%v)X!w{+i$z^iB> zD2-QDpmIAfU=d;@=0$Fo$fBVtD+ZeV);o1Vpas9++3)ZVFYsKmsd{Eari1aL&bXJI z*s2q%I(RuKlrr3!7zVe;!sLY^gD)I7XM!i6?S;I|OJMtw(v+>)cbO?1P$vLxEI zo6acjZ}wR>|2~x0^Kf`QBJxR4Rc!(JG>;PSUa4}YH*ECiF`f+#Z}3`LDvFmv`f7`) zHC^x<-xma~ufKiIm)DUYVyN~ig?8u(ZP+#T%)#jdF5(qtz|By^X|B0*bUqYXM?)v- ziTPKyL*>Iu_H03(LD&JDDe&N{@B9;RxzxUdw9?0S1-QIhyc#Sy99$GrG!DeASF+R~ zO#Uer6}a3_ZZhe#`;tQj4l3)^NpBEBur~J3a11!djEIOjEE8X7wh0Q-M#DP~^v4|= zAIESu{h+Q>oNT+1<*((2W?jxYxIzz$wIT1$WK$@_?tFi((Sc%yLL^_}0Hp@I7zbIw zIM3L~Da%NM7H_t{-?TS3C~KeVC}Gub>b+uZ##tgg8rag3>*`|TVbdMD%#f74ng1O9 zA|Y$AzuuGcr&}+L6oMDc%@tn+rHuoHkgD#)Xq~Y`*=tjm@9+z#o$7E}#N(xaRM2zL zPY7Nn49}AAk1Fx|>wb4P%d)u_DfZ(#%i`C&wLnXq#ExFB_qwxV3f{kOlmHeAYKrB; zB%|g04DkIbMvL9GYn_uVhR=ezd)ttV#<@8IF6Jq-6PRh6Tm z#a1E@rJjDVB^07i;PZ)NA4*`VM<#W3i8J;x;W$riPQC*=2f-Tsl|bfOrK zToxFvM)?neS~DO6LPd_%L$hmsQW);{tR&N$t!jN^Jg@}IXwJ!bAkw@zG#TF*qQkC% zbzgnY z9-dPltkf5A-Knhqr+0VfN;7*#;$Av4mvTYD$YSGyy`gn)eoIOsZZCWm~5I&bdH`p zmh9##Pl zDy0X8F!IiwK@SQ@w&NRd&VXhon~fj(?KM1vvHH^@JIU*wE50!Ob~*S7qzgN{r=}Pb z)25MjJJw!Uuw+e2^1wUbxV-t-K;(|VlajA=fmnG* z5cRKB2E9w@Awz$jqmwM)!eCzw8_{hoOA2B&?8rfsa31*W1JJlZ=;k({V2T)%e#3>H z57m6m3M{;WDxgEIoKddjR?uHE4Eekg9y}3l%wdD=05NFjrW#JdpukJ7o1BtNz>p+PS;Ht~z?LW=PvI zbSrq8XSQ>96^=an*5Z}a5U=(`oo^Nrctzn%cwmA5+H#dF0(r(SBh>hAI4B??j#xDa zlO%To5uzSv;3-3-Fu?`Q5BVyN(^$Gbb$fh@Foc+Bd zdL%bjcW_cDGB?3|m3(1I%ML6cPN-OJ?t>4Vcv;30t_6UJ&<<`WJ6G*rgdipfi7# z7KmxxHdlyJ`ixHCfkg`6p0r=>o3$Q7(=X@G*8nM7|Fn^r%6}oufl<+2iibPLI*)mZh)b(%Xz zrHlSPE};c!J>l=^-UOjDj_SnngGxjVxQ3ZL>-FTz?*(rv__x<0)P9#CZ@g40pr5BeDN z&hD4mjbiN8uFEyusoAIRH-OoiGN?l6-W8YsY>KkqnJ6`1^?7nX?0Z?wlDRVFTkwE! zl>ZOzKaim2;ZqycK9Jh6Iqa+W6onwso*12+emm3y(uErRxaXC`T&!xKU9->!`+7YX z{dN0(BOvzJXsx(xT{lrvh`@Ix@}1z(qpL4J=yKy9Ojyk^e~~Hn^`yf=nf7BN-0W-adWm$_@b0lGhTEmE$1&F1Y0m=5pSD zI2V2YSl=8j284q^!^K>qXZbg0Q>EYhojh3S5Sczhq6@XX2i8HwoFB@+!eOU7)%2G7 zAY1}MzVjIaJ&WlEcthsosp8;ZLQ0~=$aJ#`p)K||Pm=Lnm#W6M;YoE~iSEmm7>Y#k3oBTACXAge9-m_M^o>4=m3Cxh9XYOk+kZ}36p}ULDY@e zJ;cLd2B|rJqo21g?vfxtfw9-};iw_#nkm{s)x z8;Q}43l?-tDl{x<-CGR7RT;APoD9MekB5or=J8_Q;O!EID1+y>97H|-P7i<90yiUp zXh}doUM+KFx5ML!=l=Yf{6_V5qQ(*Ut%sIwyP?X4U>gBaXV#K?An2W-@y4h;Y^F!| zy52xip_$8x8E(_FCb~5|(Gp}5V&awaDS}MPi{ld(ooYQTas)4%q(`|JNIHQzfpm9V zkPpojbmJ}1{*eH2p@o`OS31144v;evY`k7sSyG4uDMaTzx&Xa!2bm@i9FphnX^{A&5wTgA3st1&9JiCu4BbdOc&b&JBULE zWZSg^v)Ns_-hpv1axx>?J1=paYz*DUN9d_BPIBx(rcuDnP579$_R%LEzr^jMj%a>v zry`t`8p^nNbdloMXX1EJ)8%@A|I1xV(0i!s!;`KoLVKi@VTpm9?q8)ZyXli+Bah`3 z-4kqDM?u$))b*i7!|{@Stm_4XY6X{x3% z@_BjWU}?vSOD9)mvbR8gib$`6OY#xazG>IiB^-3kIGEc zUYt!@y1(8iH`xA27_D!kG|lEQ)tRqjV3v|(=O?g*(%ivPd2CJA;e6(wwBHxLUHDE) z_4Gx+hKLq1_KXHqYQ|&Mgc6uV&#F{lkd0`ckiPI?OP5p+m^gy9|Jb0x%~Dqr(!O-I zQz8r0<;On20zFyb)x%aK@2RJvY0#qLP(CT^?@ELNODwzy_J@;^9>#_3Fc_>5uH-(d z1#Nv0>!K#t#WAhQ7?_~`1(CD$ruOs^wf7xiXP2O$e{cGRoeweEyi|G>W9g(16dvn~ zgYqQ-9y+y&(gYUAKUmz9RScz1r@lTJFL$x7Xc;Q~bX8x@F`#_yQ*)c|B94Kq<^qUj zZ?eKL!P1;&`6c0ImRK#GSM^_V1_luqGj9o;mNxl}#N3iWm{*gYbpHQXfWFxi>rHUB z>*mqR#83Hqk47u3B%V-X6T-<0o!eS#M{!Fqz}bkxFNONQzmNf_6EkT#de%bSw+?P} zt0@_|Me@33-YhbsLvCn~5PwpAX_s2!?|Uv3;5ma1Tf_`Xew;wsEqf}xeN;I?hwRSLzw#n)?626W6R{Y$dqaUtHO#NhLaH>XDf_Yd-T9A>q zLQorJy*E0Slduw7khShB<&R&;SeIXBJ!^G0ik4-)?--Ec>rndG7P3dSfBqcNWgXzNu8JjdGK;}-C;;JqDs1VN{HGs@G$cTz*Iu15CQvew zH!2u8Gs%n&qk%voG&huV?r5t7@%tWMW8k5Y8PhLIu%Am^=!Kz3!Jz<`RJ}Dy98~@+ zN7KL_`QL}DXJ1NG*9Ls;iz_%g%7~vvbaCdsn6CnC5Kb?-ON_LO|BY9G)YHX!^xRWJ zOD3rFD_u6)+m;{T3+>prjp}~oWg3B9IB4a4>>z%|K;IJ&}C9K!>n zY4Z2U!U`Jej!rIkPREtZ6x7g0p-}W{0FlTxJX&|}?VcyW&+c7fAJR{SJtT-1 z`TH*L-%X0TmSsp?&IYcXQJpzWMotoAz5n~E0eCuUQ}iRJtjcO+-#CcaAT|BsFKMub z2iiAlBY*z^=V%b={w-i#dzxw(kha7aro|&^<0{)Z4uq;pe8UHaG4(!@W%ZEmY&{Mn zRL$aA82-h(@a5~ulaTxzDrsnrMDVao+hrb1#`_tz(&f_A`?m#6hrSV!k|FuyD%a0J zqz*)#gGclV_|*AHhcv$j(!D~kT4SR)3;+{tL#l_BUdMl>zvO8Moi-Xs4)T-AN8`nQ zU<=7-_PeKPE;caJBU5WWVZT2&z7vx2zkDUv*+tES6!m9>G5Ei=^--J+^gA!K24ScH zo4F9%qdB|lnz7mXZtXQy&h~1+LLq-0^@4^I4l)TPvly?>Q~=v2^0#$(Rr|&Qyij^* z4r`7=PNOUiI9033*WK>l?44rYJe42r#rQVR0reNU)!l-!jXX79{o1m+xyd{Ya zWdF3q3Hl$R&N8a1Hrm4b91h)$v~&sx2uP<$NVjx%O2+{KX^;jl$>L|m85pF{ymBH5#L7G`C%?6N|5cReq6YaK9SuVzkj>ks?+XrsB&vc9N0U>_ zJymLjUiVwY)=7xjqt=bj`=$d~Pjw$~W}A}xw!?y`qdQNVuy}Z|ypK*9VSP!gQqW1Zv?CVU`9*~Z1#H4Xm$R|aJng@y-ny;hOET(@r-`;jl}p+BHc;ekj( z9z7c8<;JE3h{GzdqT1;}EMlddUMONeizPiy#71Ti{1-9;{Cu%fi!WN6Sa=Z6Ka$KxNWlbMc3Nrin6u-EJ|Yx?D&GrtVmc_*%44w2o5<@3^$1Wxw;1HV3Z*uhh7v~_n%EU1CXiAF!-&h9XmZu3_q z@dIL1dy20$e!Ui1`lbr~u{Q?hJrXWh9ttnM#gX+cTfxC|f|O3yrz z=O095s+OBHcFgRd#>s;G{}8gx_yz!8J=8RZOUEHp4>+Tv1{D?ZHfV97Fu&FUPV5*u z-6zLCcb0_zCB5LUHvnx(Rxns&E1oOtFh+TY`d>KH$8{-XMg|2 zFQGSN)R5Vgz=F1=4G0y&wm9)SwSeT*>S@5Eqa#Cc|4ey40`B;9twVV;3vi2zsTB;6 z#($P)8je?)`pzVc4}1DY5Zlj6nk+}ftE_1i{xttIdt z{P`F*In59dyPPK?qE_|e7&;gi#SR4AFn<6LPe65^HOr`0Az)Dx$^N-*UP zwu+WVqDV0?xa9f|rrSEm%wY1+#+^Mbetib7_e@k%-sMapKuhwHfZeQbN3ZF0Gux!` znNx-{s9>?UT~(K9uZ>!4P~TF_5Yi|SpWr`T;V7W;R1hpCR$0y*QO?X(Z1*gh#jVW# zqI)9nf~UnJfE)60u#VioXcSK|jgnQE1R0LAMVvA(?#&`YcPvD&F@|6IY(%$x&lhCO zL(6&pFJRcmwiqg*A~7YA-muz*+pEA5iDriOuWwL25$n6=FrQrdm66&A*`hD!*Fm z9@W~BYh#;%m$&`f;t}(Df$%vVGk^vWMtfTf7gn69C_-w+(bYA|$<$4aVwcq+77Cjo zekhJR=hmH$B5gA8JZ zqjh?3zEX|-)Jc!MSDySQ{Mq&jSK^xz2B11gL%_$LBqSe%vvj`y{7sBAD2;>>XZ)Q< z&;>L%{8xvxHGHENhVoK5J?pA^M+XsLzjp@Op`BZ;zC*nw0rl?)5MvG^mss`Nbblix=7a`9SkVj+*2>@9xxRV~oPRiPq2078J_Vm5dMXG2ud_ zNdZ~Pjn=ll5&?sMMbsp+(Bwc9kc%s1> z0o&iGkGB%A)y=#sx2M#_T6Q`zYp9|KaH5F*LH(~X2N`*4@24w1h%!iw^ZK}*W$opa{q@3qHna?VX_iYkQ?|)>`oIt!pR=_ zUvlQZIg|Yz^wTt=K{sLd?OOrABj|Ca@W9?VaoE@|Cm~4#&rNDFE#C9Kdy!E?-}}{v z(a(K(gdn<%Z_J12O`Bj!)n=$|=%e1oycSANWUGQZ-|j{38c2F2dy$RDJ943%zReD^tWHH_3YQG)4 zzMWv_$i5bu$HaGX9XjrX8E~d*|Zp&zzA5V)stkrLnedmF#Ivh0sdL4%Y*=rpS zUyz#~?}UZ!|Lx0rN^4=ug=@AXwN2QK7P42m`l3^+0rCD`io{I03zx#dm5c${-*#)H z>(Qa?tccWp(_g~-1-TK=b;f^i@Dx>Gx!h;b95sXOVZ%V^&d;mD(>;#h90zb5;?uDa z14t&{^n0`qv^6Yue_E{1iRTiAm+$@3&`>qh7*cGhrwyhiighh-O906k5LN10h@=4d zp)sz0Ceeolj++Z%k5e$7ANO#3^%%nL$`h@*n);g0M8rN2lFMJzdh-(pnkz<^cm9ZM z&~K&VfZ57M0DqzQpzv$AW2u6JX>g{|-v{EMZ#Y;Utr8R}He=8Id&&Qi`SqwvDgbRx z-GZJVKnpE}D0jgUG$ut4s|qw7L0nN1EsiNc}x;pGG6X zUF5ng)#XJzad;e6 zv}#<+KqjBT06s@+Mi1+yh#Y=dHy>nl$X6dIT%WfgL41&Sp@prhU`ci&_Xg=a5z4vz zLo_9vN^Ax;YYuT}DS%@ys}4q#P>1Gz{YfIQ`u0MOp3$$R|9NP}v~~LRpe^#3zlYo^ z!$&Zy^pmfGa)+HH6wq#G0hEJAsIRmDKKQ73?)Uoq=e2?#ty_-g^)7X>sjmr)%Z_sp4T>>pErEN@G8#HBlFak#+Y8ife4aneRUZ$kG~eIV`0I}#;+OHRc_5Al6WSab zC>|_-Q&}6%yGGx)jV+>;a9UPH-`S)&@CJ|YIJ0(@2Q)GKtzGM-TH5Ru2PTunKPcjo zMpkMFci@QNYz9e}=@eDxa^ZgTdt|TMXz^xme5PH-%#2?k4H-Fi*R=acPtu$TF|wkj z$d~j;1hQ{cRMk^iiZ1%68g74M`-9tg_CafJT3LTA;twg9or-!)Re#z; z<~6rlrl7-o57_cJ^HD#3cfoyb_2b*Ci_bk`{^jP*0=f7tZ*5XROWt@60Yn{2!|aG) zE@eLq$el+9M{+0)6;sG7SFW$$P}-_Tfm6y`nJY$iqr8G=J~G6NI^77`644TG#Jr!N8!R$L3$jrkG33c z!nj_S%!vk}j(GGAl(x=gaH1ERLWR5ah-&Mui0eNX^eycf94%}1d~TSn_AIB$)>=rq ziHGlZkl!i)ih(=FP;m#lU`(6KFzG21^`o9%>TQid-_FgPa*AUNc?8n&1EL`NXgI?rv9ubCr*U1rkTTcTIN4h<~B&-fZB!v^%ST z)L>U+vOC6Wr=u_Jl@>V19Tpjo*e0tDW|}VzR+a~Rknh4m*jqz|zc@y9JLy4@W02+> zo8!xmq+C#Q-aKq>T2{0Vb=;2)Xirs!wY}4=Za?btSh1Xqml5%r(0@4e@Xy(YZs7#J zc|3kgroL%4US70obwO&JLqpXzML2LHa&o2YFL(V%ir0t8O(CJuYFm2E~@g}`Cm&8>knn_=}-lsf! z9UriIaQ%KrjvSM{hVQW|K9Z&xwCwtc|8SEucfK?;-G!se zxSf89YEjDyNk38vXUzKA(>ZYVi(V0z=88vb1yM|eox83w+HZ-uf0JmfD=$@WV8D{9 zeE0cTDe|U8RJSwlTG4bI1KZrcN2fnUU#g8?Wi6SR4v%O#5axKa$3Vd49P&9#n&zch zwyDFGkB!U73`c8JE`~oC37d{b?dceLw6%OIH1rJ-Y_K;zJj?ewFt&bqMF6*enI&pK zAgNIyfjpb0L2?MhvB+Ytg#nU?e=cCB3TvZ>4{w8g$@glBh1;8fxMV&*@0XM{{8L2h z%WV+%<70ZFFVUr?MQnT|oa%FV>EG#a(9YYkMJ77#%LZ;XIs5`Wt|->(u&*Y`NWA{o zt$rn{uP_|hrSy-h2)Mt$fHBbR_9w~+)s}o@-YFg{LWHB-U(P98=LWOmJ7<5#0gkF) z^&lkqe-^0mTdL1bzjyz|Su_55!gk^?_6@sZzqjAaM)x*9l`p~CvLEDad0&-67v9Nz zVgjG1Cw=ZSN@OHA@t@nmUH>7+fHVB18dE`!06SL8S0A_XOd0t-H|T`@B+0IXzr>#D z#j@?VW_!XJv!AM7VeotyPO^!PDG-y{t9Dv#AMjhO7f$jkmY1%1no1_$PtQsjE_kQT zl+DMC23b4rym(9XZBD~w;EFVvH*x)%x%K^=|5ZUl$JFs2?}86qZ_E81zcWLSHL6Lz z)W911en*dc~Ueg z|7>gKKhFKqw0p(I564sP$YpIGqM z2@k4h4_WgEcNh6&1PkXi{eHsZV+Y$r#%l*WEgh+jsZge8Y-v6Sh&7D8{6?f4T0;kj z6(>?pzA;vHy6nenzn;lH-z=GHvL3UkFW2kYxIK>_P^&{w?FYxn|5n%7IQI_ktGOPw zLCPm=ES}6{a!=&F;|C6=J9U9XA}?$hTO$DcZFJBwRnvi8G_5WcAXWwUCE22`*kGOA zWr@6U?@f3QYYlC)SY>ZD)ruc6p)`zzGV=O$;W!>IPAuMEsTu0^REJ8l>DR*P(TO-M z3)*x$d7o&U*3St2ukOD6x!DD}Y{nE%517Z9MxdYZQHla;=|gCq1L26o0L`Q``v;nr zH$puV*O4fwYJdOnF8DExmij=+`EePtDr>HoWeb+-U_znn0LH8m#>F{G*f$Jn9Vf9! zc(It4Bh`IOZRNy!qOI%OR29DHf5UQ8!TZb0ZkJbtjVWr#`6*C~kSJq%;BP*hez3XV zSk`NXScA@xkLkbvHF_$!2&Kv6Fq*GO$b_J$wE^yWw6OToy;8Yr*#I30E}lifOsA?5 zi!9TLtQ<~bp9cnF==^cKJ2nM^@<*n87ZJva#Eo1*hs|i=d$+sQR-@WIfts%>Mg3C- zYFJRD+={KguT;zHAUE@-RXW$Nkidlb5Hc8};sd0+7n5{P`MSxd z^=>u2Hk$H956$Zi|BYhFA$7Y@x(vPw5k3y(##e4sBH3-aV$30Pv~2o+3yoLYD19ok z{N72l*0#j%Y#P(3EdCX&3ZR1}&Qn56VX&eszHteQ(mc(prvh2Ur_~&wl;=?YZ~@ z!bm3`Rlv`A#s)-mBdnR|;6$TR%;5=L+h8j_jL|XUg^TIxkVAGPHi3;`Or0dINN0#FI)eE5$fb z5b*1Ag5|5OlXy5Rb`QM4VB_>yRQxty@6YaPIWC)YP0{CP;O)WJuv{B%j*0F%>xWrs zHOJT8IV*8(ikD;az!P;vqjE6u&XT7ua!Y{LXv!0q#trm@g6-kg>7Ne-NKE!AN`P|TC z%7{zD$NWUUb7R7>cjwZ~z%-X4r{em}0#o6H;bs~!#?gvOPg{e!n&?>Mge{p}PdGwR znWpc4=M#Vr4?X%$jak=&*{B#l-uA=x zNJfiW$azYI)6191$sd6( zG%O9|aD;XtxA$rpH>J>@j7p_q5e7h`Hc_vM!~kysRona+$I5~6Kmm)0zYk_4@!hEp zVL9LYCE8CzgZ_W?=Dw#1GoJT()Ds%s5GZQO_bNU zlX3k{ZPYIR7a}~;sDkt_I+~*|$=dP`HET+u^E3Ob-J+2awx{qFUz{;qsq5nHXQdUL zo}LB`*An)A^kZZ%FD1B`Y#0Ml3&IO1wXp!wD%9B({X$QUKZ8r@n!j;1+TjCKvJ?)9 zs(nzB^1Mo#yB*#Y`D{$$c6s5l{pXX#FGW*LKmFIQsM|8rX7l0%iWn!O4zOXLl$?u2*x=Oc=}&sBfg02nd>*?~+-ewWwXr`#$Qzdkw7 z#n>Eml!Z2vVjEx&nMhE^vZMTF=P;d}e%*dDWZhcDF&Jxjyg6M5lgppDD2xhbzN!{i zSiJf6dXNQ*q{O*Zzn(1M9#6m!%ZMn|9-yoicg0H4+U6+g;kY`?dK@4IiU=U_{*f)6 zFQxB%ce>F?1oM1n{rVn>#Ge?~czvmfXY||1?T8lIDL9a1Y6xB}(I1C7nfu~~ujwGN z;P23)9Q-{y@R1GOB-tr{W-E=@+jG54#9>vn&#ytF#;F`TJ7wzvuDW*FISO9tWxXC+ zk2$9k&)|}3bs6fOF@`W&D4U=R=kkqDl)}q36?dm~Bh*mXK*CxWfM_&-ycy2S^vPhc z?F+AbiqlN`?0X1Kcpv|#BO%b}Lhj_m*qO+Z-hA+4(`kf<-ntf-6alJrbYiKRYM3eL z7u0GfGpc2jlJ`Khkpfg~Ppnhb%yedE!|_kPVkqAD|FHlrlx0HnFz{*dLG-?3Gw^MQ z)$%~ruIrr`*DGCUFOGkmEr$=S*XlGyq0-aJ;_|*`DmpQ@K>pAV{jrVZgb1I*g@r<6 zXJqa+c+^zoaoOMNbNo4pjr64Hs^RG9PoeTjoRudN@UOS;6i7B-pbxBYJ2`raRhHwb zX^)Y1-Q~=4AO!`SyjF$F)~~ei064R(+nZh13S*OJW~)$(yEHAz?0UOhHBjah{fzxQ zWkZ3YZE{14v=2vUa`2P`i@K$~z#hiD+KiZLqYoT{juksG_DSkm-Y2A2${&kIr+dB| z%?!VO+9U?YODy{Ny0Ik7y-M5})vz$qu1%FJar`6a*7FKRwWU_7a3PY zP$z7KTW*n5a*4K06VSU$P_qLvkzo`(o=+|r$3lyJ+#DB9BRgo6t$Cp#=e|Sy5ZShr z+x0*_mZs+&xpBh}C(}p3Liw8rz>BQyxFlUyg~S}p~l?{<~9|D6on@j>20 zRp_r>&t}i@Ij#~N4z7XM^JfWBEb$K+)A>|5Fy8Gi%tIT4be1@Ii zM1E-|;!!SAE@5Bhe+O+|KbYM}b4PG&)$^r)XHa4el$Qa1Y$9P&@{_KwRiU6;_{@9h zVR{@FeQ%!bbP7t1pSnX*PwMeoWNEaOM`WBB@OsJwI0A5fFs3H+a*na)5HQ7>%&>^c zi7yc-SPJCUKbsD`dAe-eXJi>+{jUrFtAWPnWD4+hnFNeDr^iy#5)A1PDvoh7eb!`m zc_$O?Ob?&V50w8J9V^P*{-3&Tva6*7Pt1M7MyG|X7SX+(0gh{@iLu#(*1CuV?pDvt zOTS?Qc@(r(IqjO>f%JZF|7*1w&+Q%@(E!H5DOcwW3yYmj%#la+low1Hsj#W9Dx2Fk z3vVoD=Mebi`NXHcGh63>vE>`sB4`KUh=03T|3wq;tHhDH0_dmu- za)J+k+cX|F1G2`j(|1l5u{F6XR%Bn{KaBd#dJ0`pDByzKUkF#^ZzYx#s8RfKafr*^ zoWKcGky;hgvx||eT@NBu9`>uO_v2|!w+fBPzMo1$zYAhwx4#X{l@|B zr1mKrD3x}Bb69Kym2|GU;@oe%p^z+}NA~hh;D{hoqxGh<>eoc0X?_3bqkrnuC;lm6 z5pmuXnr)s+(ooi`fY@eYIegAVduR~WxrauE?5DON3*>n(7(5T{W-s!kot#}9_ zf#M26CX4I&pAWNJZich|hZ5P9pgPwumE5hw?lFH}L& zPhC%LhQy;q#pp=4M{nKzPk*=k=Y({2w4dOmycJ9nwIwq2KTxyNZt@NC(ewVf+IB+| zm-5eCE$Scsgs6xs8!=1%CLo77BuSk^1kh5?8w8RqcPkm7pUieNcF%SAWhT$4u;J4A zc>|2}PG-pORuvp6K4k_6<;|OMe5ECj-KWCf7wG@YV2K&%BmVAph}#yA1B$5CyNnGdi}a zU_E!GCY^dgEFX1t=dFqG#dS943W$Q>F0wk^`37~|7drSKPrUWq$row9XWR9(@$U3{ zVP=8yD=HQ=;+%Ku**%380-g~ijGr!3IA(k=`1R&0*0U%U&*ppL3(F=cQZb@H+%GfiKNV;EJY~An?1jIt^;g_RMEQFu)Z+c7Hb6VtgM*qw&M~z9%f6|36tKFu|18XGv(=l-}af%3V4!kTR(%lxJ6f7EBhIRSmMf+*RQOdQXHSf@3D)m6 z73O~-c}pzzCQ5*!G8*hv2n`xjbd()8xvm8U5ug_rsE$XdxRK~+UAtA70p5E?Ay=>{ zF0$>Kg^>()R_Jlu?(g$uCi8DjoW!!}mQ=EB68e8qr|r%dzjp7KX~ScA~et z3-np2oml25rV046$afGXM}YHS6t|BJjc&Z+J-!^2k_%AkvWvp`$-( znf4uAok-M--xrhEM-~`q+N8;x{&guDg|{!(9GosCN^DP<4>+0b&ZJniM|9uL9BcX3Y4ND(!jB7FI|^-prl9$C|~Eff8iyI zIBMQ1U%Pfj5>ZVA^@=L^ljNEj9lYO!~EP zNs(l!d_D}H>Uf##PI0391bjCLKr=?Ps|_TYB}xfKsLXJ=vyuRFE-a~$N~Z3v3W=`SCRcgtK#AFdK8%)y3^l z0iAecF2)G`1;Dl|fYAoyB!)@6Y=1nM&~7s0+E3vyUA(Ka(`iZnZ;WmAA-Uxu2c8lg z(#I4|MLc8soAYS;>5|A-(WCsXkVe$NT7c;#TMN&LXmms7cU%DKDMU@Ajk;*n45juX z?Q}|>XbJ_*>;1mYHd?_2Bf4hK+wD<~Erlk-jK3cmwt;o1B(fuYT9t{{EH#2;xjP|cY z_@ScZVCmv8li|zwJgqWrOLhT#08?t_w`d_aYGFLXad-6gO2R`Lfo<_tU1=oJ@E_7Y zP_ohtsQE4H4w8Ikke;ugV9yU>s^{SipZfKqKMAG83MRJ9(`QNbGpBmAvo=^virqo^XZ(FSmyItbIhL&`*~%lMCi9#WQ9@Q0(mIB)O;a2Ji4tZUl10 zDg+4T)C`bb9AksnR6N?MpL^-pYsdt_@=*{6=Re(czmK@mR8NaS{ z4^JtxeQfojmg$y5M|0ES|J(7$IXBUyZvzQ@1aq28f;@%9`APFRNH(TtOFuSy+lz_i zSae$>)&e9D^JrnLwF-~oEJlAiOWfq`Y9;U<7*~cczXUL>Qg{0}0XhMeE6)RBc-Z%9 zjDMxVKXw0h;fbynojhiL@!IoiIWypr`u&ZFTmXM_y=9pByu0w2b+;#viw4!wI%obA z)8HNKuTf`}F$q%ak}Kl~84;#eOxUDXUL<%92)Gv{Dbu|U6#fOEOcy+RSqA2k4t#3U zC)GxWt9HjA+)!;=e`op~LyE+fzUd6>T$`7fe!l`uEx?IEF+oMD2i?59fXbAIy83Uu zn?dV=+vsg}+W&e?e*{79cljryvi|Flv73^RG8-iB{JD3zROiPEN*Wc=gMMRET8g%G zKHV**g*X~|>Ism$N#x*3Zs+hgsdxg)oE`-OIvv@sBaehStQWi>3Tln>#x5sj89jw~ z8E=Uh$?GOqYuA++2?$>q@D`U}Y@DNEFKPB?B{+W%7ng#AXFdMDCYwCztzAlbnZfj6 zL?7mXsCU%0=-XiPJqepz8B2swER{`sz@#$3cikxc{a_;kBvJw(={(p&lUyiJmXb}4 zlNF;jo0_(9`Kun4JcIhSg^kV99R3Zld_JnsyldeK#!BRK;%#(KO7$rzLacXO%F(z2 zMYbTHy&jp#B~cYr42%t%;jn@6jQ^x~^F0*SV?69DVpCF@%G;LJcfJFDUwm1Hhfhe5 z7Jpadx7R0UcU*o@m9jeftX-n<;?6Pyaq}FIvg)rGz@wM3HxV8oc_%#>gbpC8YcZD$ zAHHl2!%;$+Xn$coQN_Nr6I6)yL9c&~|+L+=3ze8!RsNQ-v2LPyhHd=I|$E$uDghu&C;BKf{>F`5tk^h;11o0@+@w}i=00=1Ans?$p(ABtt#YPNZsjGvrkk>D z_Kc5#@P_SjUB29EGVl4+<-v|D42M)wZk2m?@pjIzy%#JXmpsnheOH16y9;>UVo?j? zeJ355gGILU0X?4J_-ImZ5i@DH}g z+tF%ibR>4f3{3DFcMT2$1M@+34fU4!WKhSy`~BnoUStX=m`OC zT898YD%drpEDGg$$l{^v%|DfQ0QD8N_s~a#d{s3d{nYuujx|$%`9GQTOR1DXjf_~Z ze1i)EobQ~pWLfdL$mI0F%TBi~{_aou$4^akTygah*T%1Nex7;(PiJcX-a5T{w>+hQ zheuf-b@_(|7sy$n6D4xH%>THXK!_VAlkrb_{(Z#G7^kW;hAu3M40k>0TJ*>0@#a+K zHP$ymCnh0E>|_+%6!xzDADJJUtsvR{YyOn2o0{%+A16xaqni#r!*fl6YRR~Ma-O_4 zP|I?~1dD|b18BZO1}FvQmN(zfNRWosWb%Vw)Wsm@bUvnI*nO-EB8()HQ8;r2^VDT3 zitnSxyMc?2>t9=r$!8Ng`!!g_U=;&BOFHgCU1{fsLicC37f4Wm=T)lc74j4{jV@r; z!=C^N)PKQAtUskdX+|Ch2Y)5}$5`l>??eSj{&w-C3W(++vd4b^&4jrtd0&jkP z?DFGt-2E@0{4|oX{(?d2q6@!}A2hrGkRv$zJ8*0|Cn>nl?0{KQs5G!ilqj25ULWe? zxH7&o%bM`qZ&SjOFyM0s6?*Pp4DQA)VPy?%>#*?b<`&+qbTzaNl^)~29I1%0f5jSd z1tgM2l$m0}*dFc}0~-@)hy#uIH*^WRu#C$p1bf0}MRpKqukJ$CvXM2Mq5OHD2GDX113DXZ9^xtw3MF{Fx$%fBxj zE8Sn4KdXMP(w1onR8EbJ$==#g+%IP0V8lV&KCdcd`+u7%FFTK!OM%b5yf02tyVl5 zKU!~c0B_x8x9(+hz}+nvXKq_ov<+QmWvPo(tRC_wG533E5>NMJ72oPbg-e z56Ry4c0;;%6LllJw=ugo5m%+mp_*gNsi^<79%NL|{Oy;(XPMM2iWL$e0en1u|Du1` zMOu;YLK}z8)*|1?T6dZXfYCTJGQ#D|`7_w|q8ZcCwcdhmFcef3T#mmLFnUPT zZdbtC3bYrjor7TdiK?{Cmnm!?#Wo*qGdg^|eqGO+nUK_eAi_3yITV5{#+=SLn^DX_ ztT4?C1eUL;Z%|31q8AjJBf%7^3=>{{Dqn2dM#se1KB_VdjH9a|C5>Z)|9Fw%k&gXe zjLb4TqSM0GAU!Vg`e}re$Yt&d+~n#rFq^T`LV%kX0~v%4b*YLG&Zq(OgHfXXx}-8b zDqVMB&`4#8yltE2?z95qXXfU{vbjVp3_i?*H7*e?fWF^aaDr+dgpmP6f$?;U4y z@eADmBP{I>pHpcgqs06IQC`NZ0AD-8^4C)aaJ(K8N$WL`!$=leq;K)g(tkUb(@bMd zt{516{yOuZxvv0nz~p*=UPdPDnP}E{EuGUgBi9d*>`}tQkB@$Acza;~7nwik)XcYf zn}1j=XW}ruzpL}z>BQ+w-LDQA1StO3*y7)YQHEQDpRtSdk&Qzos)-vxC(ImfNm37lP z40t>83}ch$ckYrv^>oV0BY7+O-pam1N`K7RLANG-yxieWg@#S zd|7{>`PhT)lvkM2aP=mZ`9ztUuB@|K!sGiqm!b~zfJ})@&IgdgLR^&bQFQhM*sKI> zaU&PF9}KXC8hhlWbxJ-LAUf|RRlYWbqP?HK5p=zeUE1m6qsC9c2}iY0>7#rkyK>#T zeV#gH~-H>c;^AG?rK<-*bKTQ36(>CRi~9#TW`1#(y%&H z{=Ag8%&e2+f`gJ8cZ!NKwk2t_LnX|L{?1iV1qFZ~0U1rHXz`1+-9|I2Nf=1Am4!gW zd)N=M0DSFwS?38NgjmQ`y5%l?cNMw>n*&)uY}PXsK25jaJ=WyR+`zb=yRwSGn+;w$ z=AH5Mp=GMYVN>iwGq3>trLILE-QGsp88h`46XxY($nAJ&|9k)G9FF&&RpVs2ETs>vmf^!x=7DzN&Ci)IphJ?p);uV$RP?OLYzBn*NIUo&Z_=FJlPDK@ zu~QTP-JR<7p=u`xBJf5MW;gXnhQyk^Sa!pmG(=I5T83Y2Nuj=9zONA!{;6k2mvT)% zLEKdxd>xz4w8se4UQ)$_UtFxco!DNJ*0U94P!~B}57aaC1*t|Lx>~qS#Mt}p1ZINk z*^>YK`?tj;YJPFJIs`^;o}aG2JZ#PK@%z^+PF(N4n4*OLR4(c#=ly{Ik;g!ad(WGnCi9S>P+FopvdyXu zG&C)vM)B~Y{bWp}mZPfspXzsnSJGd`(NZb`CxBRP#GH4}J!%G;{h-6<_4v8%GvzTR zl9&B8rHKDMJ7_!720EpJ0;Z_^r>r1rlJWGmj0d*mpG6Q+3q9`IQ9_iI|qu$_DS_uJ0u}6ltopi}@W(D;nXgAlfxAV2I2THq?<{ujn z69%a<9!I$A=)Y?@DufuRyC#Pf$lN?*#(m@UVy)_FucJ59buon2+qs17X-mIlrAlk! z*)m!+;FQ8WK~M&cx7G1!L; zVyvvn`S$Tn&V#xO*MDozPYohZ2R^7*nRqm(wM_x$$ZKiP6&~W20j}!LWerqAS6FX0 zh^J*9ll@jHYzd!F8`^%UGbz6=QaSz`dL4^mko4~xbG zZ>4+=PKPplZC%u0P*Hg-i@wPB)3v-dz^5$`uqWnlKAJXf_P$5EB2S0>uN@ex3yor> z-tIV1CC>JgDF-%*@Y9DrPrSm+nC-~?jt2mzu!n;(7@5{h5fgHV1i@(8XVK^HDUQM^ zYs5amV!xhBl)@-PjhHQikI;Y~1IOc-dQ2pa&b+}3w8mGPZav4|xE~>Fr=1n6#wSUJ zyhSI)X8z9@6QV)ZXsbR`bhyaba$dm2Us3c#id*KGQ?1V0bQ_R41EVMuxVF#-OD*C# z2e+Bo8F?_@{~ehE9;LmPM@QXPhTH^YRuE2_g5qT0IJ)Jfv1r7XfmPZuv#$3j7rt+H zi86u8HsIv9e`M^jQ!6l@@f4+dgB|NNU#aeSw#$&xLhbN&1wbq?{VR+nq-im%Pdj#l z%?nkcy4ABjE4LS=#x?wN{IyN~6mqUchp=lk0tH@#SWR~iQG*YQIfS7?D@>@X?SEclOg zORx%(srCQkB+ z5QSXeQ~HWgoX+K^(R7haYu;Isy-&$Aus~>mQmOe}t?utvdU9-(g>{cK1LO6$C?O0_ zFL@A@Cricx-pHDMNS^|uKCJv|Lp1F~D6l3&<@=n%(DAH;O=uou`!-3{Q<&2+^yEv6 zkdS-B$w3Pd$02d~HUA~_r@Dp6AkP!$1(VP+!de!09u{Sj&S~)?q3b>YW$a>X*K^5I zZHK6@zLhnhulK@gYFNJse3BS#%Ka42p7@ZNJ$r5>>h3L=0sa?t>s(&mAQc$YPF&t< zH~sj!9ZAN&;2!gNKq;Pc46-{0LRsK*zjbQO^S;++HUNb-q4XmllVMS6)v?X%W%86Q z!g}6*RYb~#t3LHKgA?e--mSD+4K_a@i5RjBj`NQDbFgJ12b?f}<`Bl9A}3yP#J$QQ zD0g3w5UVtWO{V}C&rk1(VU!3v6T#o+n2zRwMJ=Y`$Kj#FaHPy90HBC@FD0%nx>U3p z^OiU;udG|N!){%?!Hb(APD&UEXpx0|Yy9Z9V ztai&;ZHiLfNr=NgF6Q5W;NI{ z1i$2?6D#ZM42^x`1C4Z7(BB3MtgBpxOkx`9_n=~47Mm5mrKQ__9G?(Ag&fI$-7IT& zV5j3lOazT?uEgP2IF&_s5KL)Dl&7Z`kvpbDr=BeDCzYA(dX7$+d=2imubGFJQbaEo zb~Qj$XsKOrgDSdi83%yO-B!web9w96e^hv&Y& z!Mzc7=3Gmo^&tybc4NV<(?~*1EZXYd<4ft~3UV^zi-lH&O-VR~GQF zj2)69O7NY_qg|ML;9jaj3RbnYg}DCgt7>&z`R*z7&6B|T!!|2e3zH=f8*xengEU5K zl_eE;qejm{r9t(8H1hst?V7Mn(#|HwO%ZO#IScY!P?AtEVjJv!-n4$f1^M*@7XSh| zeXohTJ+d19Vxc5Q9bgV3JEZH#2OIh`Jq$H~|LoPEsPHa0S^8s>dnQO~6=<-!{wWWe zbU%Gv0a~rn=R2=!+OcrEOfd{}loRyaRB{dmb*R_!{$&K&Hd)DYdz~;lknUx?rw0HN zXHPHeheE~>KlXaQ&T58!vNdlCHu*+3%=Rncbt-?h-VN_x+1OISl8j0)9v_jXvE*cN{o_KX}W_-F6Xw9bBV$_BTi{JG5oGc0A#QYh!3=<9bc+fa} zE1#m%Z1oa6@ORGYY=dcVTPd+;lads&H9;x<0!XRQ&+|$2dY;g4?grmw=Ii@Yl7tV~ zN^%&P7)-2eR--zm?h-kf0=7zO7e#Dx2ugAgpq@;G^z-r;<5jLkYx<=&!rvT(!w(PQCaAr zt??tCc9m>VX?^516CZwd4a})X0q(%OpiMq*5R_+We2vTR(f5IAtDO2*fIR*?aM?q{ zEuc4726A2k)Ceg-(Er{n+QM<1h^X>kjyyz?Qk-r<>8LMrY3xOqmGq)YUj%uTIr`@M z4hgSPcpTbb^I(q#7N>3S2%XlB-i_#&xEA3vv;j_4Y+>NoWio>&9D`+3#Kkb8#@ljj7R3o6_ep$NYbHY3Om)IzcQ@B^yjfCB>3A%l|W9a(K`>lW0lVaVx*f)3tSL%N`XNv6)d*xCjZ0K!k&zTG;P1hTCXSBUF5{% zRc~w~M1d-xfhPgcyb{sVx@c1k1a3^z*GiVpDXvB?kFGcn4@hq=-U9LMl4|OW)^PCC zKVwsJ>fP3B!UfGeNlmzs`mVBAlUE2ZU{v8Rff@x`aK`=;84BbFTW$ePtBs4(3Zxm+ zo%!^@wDaz{qWxqc0qA|&>Msje?@R3qu#AjM3H)Qkn?ZYM2%h&Jp@5WMB500 zB`eA*(d~*BQC5^yR(U7+{+{QZKjx2_dCr`B=G=Qv`<&q}18$|TE0T89Y`owi(0wa34$b8hMs-&o)Q<0){Wcg zf82F{32F7I!ru*>p%x0<)JK;BA@2uyRlwCdJOsbV9OtoyHyduu65y=Aj8z)Bf?r;3 zvm)EgP4glD_00@;b95q=F_7N@rNB&S)PbjF(&v&pk) zB7VxI{6h4;Dkts`dN^71VkfndCMesQ$D*8%{MiF*Ymdi7EPgXZA6yyE{e%S{lY&QC z9owgr&;V>F#&^V(D9T#Sf-mmPD)m!*3~d&)H&~PGO$UGW-9mxkIMmE7XjX!0j)l{N z^#@%k{$~dno!)nN1BpG|d&;a~HjW8T}pRz5W^HQ#nxQK1>^ zC=ah+wOMA*&C+UkuRE91uC8jgGT8@ZG*_JMI6xmS=DFEm6({)d1J)oN#2B#n!JG>@ICBQ z;0jEx^G)$aCGe&+m=8-~!&^mxvU?XE=dnOe7>RkMc z)R%aEP)4x=^eTG~Je5`3-Y#g%Z)-pK@uT>DEtb$8kzHt<*w!+8k5-|_bj+>89qmQT zvZ#qeufk3kOjhxE)5R>*rNR`Y!TGa&y6`%Jl%ZJ^Dcb8`k>>`RU38fx2v7_zJh4b{ zVtqXR;q`vqMDVa^Bax{9A?&)|dY;zrg(eeK^Gl_$IwIA*Mm;m;_b2egxBsp3}o^BGg7A=p(aNr@n6g3wU>SxI4QgL7Ktr3t9%k5 zWH-!@@Kie47d#jnaA9w;t@Qieq$hA)zS2)mKt3nVjc+|9Zk1@?LI-HUT_V!Z&})I>jK7R($-`7e&$ z^6^uM`(oF#q)Qb*chn-Ol?4C|4%2=D69$Ay3vXdDwzrdKvV(Pr*Oj;MB zJv&gv_MQmpu@vNg6I^mHciTOY#i`2~D;W5E>IML|x!X&~Pf?a}xXatVC<1bt_C8#B z9d;KxEcq4hv)YOw3@z}c_^++61aA<_Gwf^248d~$ersDxzxfHqXZZm(}PzHU`+G>LH#@v*Onxe!?A(yhJES(;6b z|L6(ylnGT@2hW$N>OMPF4?^8X(-l&1@)L2tg_KllTNug7U<5KPQ`AttLR<%C~v!#x)g9Z!X>J zDY7)?WRACmG2Ocx*k-+_6u`QZs2XTtW4YBa?%YXwaj+U@@Tgkk>%^Y?BPQrW{l_7k zW0*qZ_*K~N2+DbbW*1zmf1L|C87T5q!5KdbFUUebUq9a69kqa4944*0s!Vq4oE8$S zkjT2qzQ$OiR`8-{Zo~#w*4Qd%Vvz5u;Vf1x(X%xRtH{`#%J#^;c@z?o^+g8xXx@X( zqkdj1Z2e>Sa#I_jNs%W`4cQyP6KkD&uWIZX-gdj#XF#!jEA+M>MQf$a?Gpi=Hl+OF z_YLI2`3yGbhE=y+q!t;oST9NS{zAV$Zk3eo$dAzEW{Pxa!7p{)8K1e2Al z;?~16D_;v$i@z>~RwZbc#oq&L#1cdhWdGPJHeNgOi&f7u(1iFi2HH~meo?emtzbz7 z?-IfG*bfw|_|0gf`hGhE9^mAx)FiCi&XrM=HX*+DQpJ0d9j-KG+b-esr8!&G(q@IN zq4MtsS~r}=S^RRA zS%^0(Tywg3UrMr1e^v+dB)gb(bx{=$6E{AfhD^97g6-W6zzJk8;4!V@1!?hmW$8p{ zfXb-UUa3)x5!3*BgPTT&h7|dWLVbgx?-vEqM6_9XA$d=>ZKvFT%qpYSJH)c)RX}xA z3sUpp-()J$EWjv?mdxZl>^Prs#&kE2Wpkl{%qqMD5FqmBRDZiw6tR+-<)v`I5>9=|h;E-RyhmS)-3S z#8JP-uJnwlybITFZ2a@^%jD$V&)oLtcGV*%BY`<<4r$Ajj9$Y>rSQ>q z$#yaZwp~)Y9o^8<1VL|N@jY1QcEpLGxTs(aRr3-Nd=2{x0F z7R|77w4S`zbTEn{R+-Czmikd@y5Y1?sEd#yEg)eeS^UpuF+Pw|bDQKdt+_v~ZG~*) zpmp}4GQe)3+H!Ul7x&_c;6B<{;-GjdLK16+Hz= zq((s}vI69fc?7;U$nid*r9i+G z4P;z3e~`ogBXuR2JH*fI?{#!D8EJ82I#<6hqu~xGQ6%iVseqE}Kz~Rm2xtE@u4A}~ z>&m|Y8PpdI+FYK($TA)&X6IoXowDEml>>4+f4kZzd{aOiCeYl6e96Qek#mtx9i zmX+^~O^g(Mj2VvgPksE6{#?qz^>aUN2o?n;r~F6QQ6xwR#Lx5XnM|X9V25}Hb=*&) zE21&oL1G^kr4Ed_MCH)u@bZ&COeafLht;T@nNQit1+jdlre=VqeJ;&hHYgx56ukL%^?2g6c`&le8_N(#KBBt7=6Z6w)xRL zunT{je6|aVbWL}J4G9AM%#QU_)#;5(1pe-Y1-^wuM!-kDDkhl_A1#OQ`bfM! z;y$HJ#%6$|#!&D!7!aa{ibnQP2AFDtKuq1mS`3Vv`(z6z-b-pjrMSldw0ul})m2y& z-MjnREvk;YxH_NfbJ>5wyE~}hlJ#?~k>wQKy4BCi6u*NpB37IWpP?b^Wk~)rW`Et9 zr8P@LurKqU3Y3-9ehr*pYC&^^NGM5_7Z;|FI7dXbo~wzPTiM9Gtm1GIx3Gs62m+8G zTIlhqZKUPR$$zSSmEH;YC3^6bIHI|wzme9iE5JMX42ulMim5U(uZ(xqMH5cjx#`s; zWQ9WbWvy=BCO+Orf2owAIS!aHzPKTli*^SbvF_cIN$<4Z91YUGphb9hK97dFdo`_gSUcM|6(@=~NI>?}H1me4I127n~5rmnSH>!U|& zZgieMEH=Ju1~&haf3l#Bz~L~I6BDOHR;(d%H4$(%B?j)|+2#@4@M$F@7qYoM62|A^*d)W}m<4z>3_-16M?R6B(H@4TiO zugtz0qtNK)SWwK-?WJ_qxaCOlIAH{jH%dKEuh-q)!hE%x>J#vz*md+x7NU~pYfFaT z&`64L)|FwYT}xO-Gc)|tIkb_*GQzt{B2aEl_H>J?lLg=24cA=BC-5LgsVAM~*=*(f z)B01vLk|>LeyqeW!C6Av){N82?zrzRO(|8>r+!lTA03+wkB3ziZA_EY^ik4TuOfEC z<0KE%Zc;p7PFVrlzEnfI|BMpm*!K9aCN^5Tm6yz5iVpkn^H>~_l|T5(6>GJyhp*Br5HKu=7wa5^8q|G2{Lhf=q}!VbRqoxU3E9%hON9rYuY+>6BsKk8B3 zoiB|Wu$#)y=+6)7ix`D)_UCoU!q+yIs-Tt^r^c2>HFh(M{~`z)v7O&rcp1O>#>+{) zMnqu$ETx29Zl{k7dF$A}2^jPUTOXh^(7_6?y*sBZVO^>clS$M_A{(No-+B_F9!I{= zc=5jh<}2hLv9iZGUGx4?fg!-xDW`L}xs}H7z#135?(FA@%~G|tK2gfx1>z62+92MA z;E)PeDSFMz-^NdyM{fd^#{WVR4V(Gpjqg3p>2PiA%o3ztP{ahL%f82SABk+D-z4Gf_b zvEU`uBb9RIRFq|;?a|?Y|A0jN2Bj2ocy>XlW*a>;loqzb8J@j0S66@dD8-}l@*XZz z`b(p39FUJo1+(r2=9b+_y_uD3<<9tVnjnps$W{#$;0O|It=(75y1_`X08Vk1LDhs6 z=DcKBRSdXNJS5xZJM*^rBgc?SAP3_4$MyR#sdz z`COKw2C*(&72=KmykUEe`EsHvOcxd79F56O2-OFLcWDP!7p?+dOZGSyao?p>?t$y) z5!;`$+*Dh{-u(V962a>eesz4{k`hD1OTBxlh6;!GKaUm;@Lu@hv?&7G=Vo3+ZBd>5 zYSxt6J5Qs=1UMUJ_m|Y5Jj~1p+>)QZu&z#i!J~XJ8;N%>yWPx?Bp#2Bu|?7tJ&|r1 zh@F@iXS6}z_0+b$CldM7D(s0kPQVrfVGJ-dZ>T1akV6e|DWH-mI&ArnfX81pvwOxx z-ukO{rOBU7jI2$ae@vXjnn`}~pr^k`0ywHhkuPiIkF-eJHNM|K0Kc~3%zytnt?YW4 zvPCIDjL5p{){W=UYVm#DuFuMCFF%trv^!>!2`it-eqjah4p)M|3D@zF3#DUmVtD8w z!w@N7tV6*|JH6x%Cb0FAorAoc2wEzIlPA#AZl$u ztH4BUmSE{cikK@FI`!KEyA$M4ZB`?R?Pygyn<|P7Y`S;6 z`@?UYM+noo#~$Sb+uLavh_yp7Y&6*#*6r8xWbB0H9XNmP%HLDl^jAJMyF?4Oe_&!x zYSEZIy<+#LYJnv@+MuP?9)xWal()BI+I2vcmrY4D;Ks!`1P<+;vDi#xo@1@2qXa!~ zx>}E>@AKtU^fk2+FATmXcl8P5g=b!cPH2|Kq7T)kG*M*5*ae5mSRpkeR7y+6)Z4Du zM?qZb_+o0jC6c#ryUT|eHlQ9h%Ob*OBxWS08cp11h5W7=cJnDk68mZRqaacyu+%4| z)I{^EJwV%KxX`^X1tE!knW#Nn>bomK?FUgTDf zoHbTf9W{N6dvD&p_GD{2{Os!tzvepX;rJl^aU|>Zyhko57bY>49`4GGxM4k-X{FgBg(5wk&dpvJ!rjc;1YsQD2KdFO&LtDc@y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Kovid Goyal + + + + + CC BY-SA + + + + + Kovid Goyal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + +