From 3a3a05be790e9975d5ac518017f15e7b84921bd5 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 1 Feb 2009 21:17:40 -0500
Subject: [PATCH 01/65] Add printing support to the ebook viewer
---
.../gui2/images/document-print-preview.svg | 14298 ++++++++++++++++
src/calibre/gui2/images/document-print.svg | 14229 +++++++++++++++
src/calibre/gui2/viewer/documentview.py | 26 +-
src/calibre/gui2/viewer/main.py | 2 +
src/calibre/gui2/viewer/main.ui | 25 +-
5 files changed, 28576 insertions(+), 4 deletions(-)
create mode 100644 src/calibre/gui2/images/document-print-preview.svg
create mode 100644 src/calibre/gui2/images/document-print.svg
diff --git a/src/calibre/gui2/images/document-print-preview.svg b/src/calibre/gui2/images/document-print-preview.svg
new file mode 100644
index 0000000000..6ffe4fafa8
--- /dev/null
+++ b/src/calibre/gui2/images/document-print-preview.svg
@@ -0,0 +1,14298 @@
+
+
+
diff --git a/src/calibre/gui2/images/document-print.svg b/src/calibre/gui2/images/document-print.svg
new file mode 100644
index 0000000000..dffa8b94ba
--- /dev/null
+++ b/src/calibre/gui2/images/document-print.svg
@@ -0,0 +1,14229 @@
+
+
+
diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py
index 6c1eca0061..6ce7a39e9d 100644
--- a/src/calibre/gui2/viewer/documentview.py
+++ b/src/calibre/gui2/viewer/documentview.py
@@ -8,7 +8,8 @@
import os, math, re
from PyQt4.Qt import QWidget, QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer, \
QPainter, QPalette, QBrush, QFontDatabase, QDialog, \
- QByteArray, QColor, QWheelEvent, QPoint, QImage, QRegion, QFont
+ QByteArray, QColor, QWheelEvent, QPoint, QImage, QRegion, \
+ QFont, QPrinter, QPrintPreviewDialog, QPrintDialog
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.utils.config import Config, StringConfig
@@ -305,6 +306,27 @@ def goto(self, ref):
def goto_bookmark(self, bm):
self.document.goto_bookmark(bm)
+
+ def print_preview(self):
+ printer = QPrinter(QPrinter.HighResolution)
+ printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
+
+ previewDialog = QPrintPreviewDialog(printer, self)
+
+ self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.print_)
+ previewDialog.exec_()
+ self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.print_)
+
+ def print_book(self):
+ printer = QPrinter(QPrinter.HighResolution)
+ printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
+
+ printDialog = QPrintDialog(printer, self)
+ printDialog.setWindowTitle(_("Print eBook"))
+
+ printDialog.exec_()
+ if printDialog.result() == QDialog.Accepted:
+ self.print_(printer)
def config(self, parent=None):
self.document.do_config(parent)
@@ -552,4 +574,4 @@ def mouseReleaseEvent(self, ev):
self.manager.scrolled(self.scroll_fraction)
return ret
-
\ No newline at end of file
+
diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py
index fedebc66d7..3b9c0fca1e 100644
--- a/src/calibre/gui2/viewer/main.py
+++ b/src/calibre/gui2/viewer/main.py
@@ -248,6 +248,8 @@ def __init__(self, pathtoebook=None):
self.connect(self.action_back, SIGNAL('triggered(bool)'), self.back)
self.connect(self.action_bookmark, SIGNAL('triggered(bool)'), self.bookmark)
self.connect(self.action_forward, SIGNAL('triggered(bool)'), self.forward)
+ self.connect(self.action_print_preview, SIGNAL('triggered()'), self.view.print_preview)
+ self.connect(self.action_print, SIGNAL('triggered()'), self.view.print_book)
self.connect(self.action_preferences, SIGNAL('triggered(bool)'), lambda x: self.view.config(self))
self.connect(self.pos, SIGNAL('valueChanged(double)'), self.goto_page)
self.connect(self.vertical_scrollbar, SIGNAL('valueChanged(int)'),
diff --git a/src/calibre/gui2/viewer/main.ui b/src/calibre/gui2/viewer/main.ui
index 59f813b2bd..122993d528 100644
--- a/src/calibre/gui2/viewer/main.ui
+++ b/src/calibre/gui2/viewer/main.ui
@@ -27,8 +27,8 @@
-
-
-
+
+
about:blank
@@ -87,6 +87,9 @@
+
+
+
@@ -234,6 +237,24 @@
Toggle full screen
+
+
+
+ :/images/document-print.svg:/images/document-print.svg
+
+
+ Print
+
+
+
+
+
+ :/images/document-print-preview.svg:/images/document-print-preview.svg
+
+
+ Print Preview
+
+
From 38c92339bb4f4a69429c4c6a2d1b8899ec51480d Mon Sep 17 00:00:00 2001
From: John Schember
Date: Mon, 2 Feb 2009 19:07:57 -0500
Subject: [PATCH 02/65] Ebook Viewer: Support for printing entire book
---
src/calibre/gui2/viewer/documentview.py | 68 ++++++++++++++++++-------
1 file changed, 51 insertions(+), 17 deletions(-)
diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py
index 6ce7a39e9d..ac9cd1fe30 100644
--- a/src/calibre/gui2/viewer/documentview.py
+++ b/src/calibre/gui2/viewer/documentview.py
@@ -307,26 +307,60 @@ def goto(self, ref):
def goto_bookmark(self, bm):
self.document.goto_bookmark(bm)
- def print_preview(self):
- printer = QPrinter(QPrinter.HighResolution)
- printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
+ def all_content(self):
+ book_content = ''
- previewDialog = QPrintPreviewDialog(printer, self)
-
- self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.print_)
- previewDialog.exec_()
- self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.print_)
+ if self.manager is not None:
+ for path in self.manager.iterator.spine:
+ html = open(path, 'rb').read().decode(path.encoding)
+ book_content += EntityDeclarationProcessor(html).processed_html
+ base_url = QUrl.fromLocalFile(self.manager.iterator.spine[0])
+ else:
+ book_content = self.page().mainFrame().toHtml()
+ base_url = QUrl.fromLocalFile(self.path())
+
+ return (book_content, base_url)
- def print_book(self):
- printer = QPrinter(QPrinter.HighResolution)
- printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
+ def print_preview(self):
+ print_view = QWebView()
+ book_content, base_url = self.all_content()
+ print_view.setHtml(book_content, base_url)
+ print_view.setTextSizeMultiplier(self.textSizeMultiplier())
+
+ def finished(ok):
+ printer = QPrinter(QPrinter.HighResolution)
+ printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
+
+ previewDialog = QPrintPreviewDialog(printer, self)
+
+ self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), print_view.print_)
+ previewDialog.exec_()
+ self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), print_view.print_)
+
+ self.disconnect(print_view, SIGNAL('loadFinished(bool)'), finished)
+
+ self.connect(print_view, SIGNAL('loadFinished(bool)'), finished)
- printDialog = QPrintDialog(printer, self)
- printDialog.setWindowTitle(_("Print eBook"))
-
- printDialog.exec_()
- if printDialog.result() == QDialog.Accepted:
- self.print_(printer)
+ def print_book(self):
+ print_view = QWebView()
+ book_content, base_url = self.all_content()
+ print_view.setHtml(book_content, base_url)
+ print_view.setTextSizeMultiplier(self.textSizeMultiplier())
+
+ def finished(ok):
+ printer = QPrinter(QPrinter.HighResolution)
+ printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
+
+ printDialog = QPrintDialog(printer, self)
+ printDialog.setWindowTitle(_("Print eBook"))
+
+ printDialog.exec_()
+ if printDialog.result() == QDialog.Accepted:
+ print_view.print_(printer)
+
+ self.disconnect(print_view, SIGNAL('loadFinished(bool)'), finished)
+
+ self.connect(print_view, SIGNAL('loadFinished(bool)'), finished)
def config(self, parent=None):
self.document.do_config(parent)
From 358ec20ceb76e8fb77882d7e89d6330f30dad9db Mon Sep 17 00:00:00 2001
From: John Schember
Date: Mon, 9 Feb 2009 17:34:51 -0500
Subject: [PATCH 03/65] Fix bug: 1755, add new Vendor and Product Ids for
Cybook.
---
src/calibre/devices/cybookg3/driver.py | 4 +--
src/calibre/devices/kindle/driver.py | 4 +--
src/calibre/devices/usbms/device.py | 39 ++++++++++++++------------
3 files changed, 25 insertions(+), 22 deletions(-)
diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py
index f573fb1b75..f092473675 100644
--- a/src/calibre/devices/cybookg3/driver.py
+++ b/src/calibre/devices/cybookg3/driver.py
@@ -17,8 +17,8 @@ class CYBOOKG3(USBMS):
# Be sure these have an entry in calibre.devices.mime
FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt']
- VENDOR_ID = 0x0bda
- PRODUCT_ID = 0x0703
+ VENDOR_ID = [0x0bda, 0x3034]
+ PRODUCT_ID = [0x0703, 0x1795]
BCD = [0x110, 0x132]
VENDOR_NAME = 'BOOKEEN'
diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py
index 8ef1ba6b9b..0da1f55c5e 100755
--- a/src/calibre/devices/kindle/driver.py
+++ b/src/calibre/devices/kindle/driver.py
@@ -12,8 +12,8 @@ class KINDLE(USBMS):
# Ordered list of supported formats
FORMATS = ['azw', 'mobi', 'prc', 'txt']
- VENDOR_ID = 0x1949
- PRODUCT_ID = 0x0001
+ VENDOR_ID = [0x1949]
+ PRODUCT_ID = [0x0001]
BCD = [0x399]
VENDOR_NAME = 'KINDLE'
diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py
index 761fe9ba74..5943e2e13f 100644
--- a/src/calibre/devices/usbms/device.py
+++ b/src/calibre/devices/usbms/device.py
@@ -74,24 +74,27 @@ def __init__(self, key='-1', log_packets=False, report_progress=None) :
def get_fdi(cls):
fdi = ''
- fdi_base_values = dict(
- app=__appname__,
- deviceclass=cls.__name__,
- vendor_id=hex(cls.VENDOR_ID),
- product_id=hex(cls.PRODUCT_ID),
- main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
- storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
- )
- if cls.BCD is None:
- fdi_base_values['BCD_start'] = ''
- fdi_base_values['BCD_end'] = ''
- fdi = cls.FDI_TEMPLATE % fdi_base_values
- else:
- for bcd in cls.BCD:
- fdi_bcd_values = fdi_base_values
- fdi_bcd_values['BCD_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd))
- fdi_bcd_values['BCD_end'] = ''
- fdi += cls.FDI_TEMPLATE % fdi_bcd_values
+ for vid in cls.VENDOR_ID:
+ for pid in cls.PRODUCT_ID:
+ fdi_base_values = dict(
+ app=__appname__,
+ deviceclass=cls.__name__,
+ vendor_id=hex(vid),
+ product_id=hex(pid),
+ main_memory=cls.MAIN_MEMORY_VOLUME_LABEL,
+ storage_card=cls.STORAGE_CARD_VOLUME_LABEL,
+ )
+
+ if cls.BCD is None:
+ fdi_base_values['BCD_start'] = ''
+ fdi_base_values['BCD_end'] = ''
+ fdi += cls.FDI_TEMPLATE % fdi_base_values
+ else:
+ for bcd in cls.BCD:
+ fdi_bcd_values = fdi_base_values
+ fdi_bcd_values['BCD_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd))
+ fdi_bcd_values['BCD_end'] = ''
+ fdi += cls.FDI_TEMPLATE % fdi_bcd_values
return fdi
From 3392459c2737f0a66bb1e0268469cdf78fd2f243 Mon Sep 17 00:00:00 2001
From: "Marshall T. Vandegrift"
Date: Tue, 10 Feb 2009 12:54:13 -0500
Subject: [PATCH 04/65] Fix #1815. Don't re-decode unicode metadata.opf2
@hrefs.
---
src/calibre/ebooks/epub/from_html.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py
index bd9b59cfbd..fd94c9ee69 100644
--- a/src/calibre/ebooks/epub/from_html.py
+++ b/src/calibre/ebooks/epub/from_html.py
@@ -74,7 +74,9 @@ def check_links(opf_path, pretty_print):
html_files = []
for item in opf.itermanifest():
if 'html' in item.get('media-type', '').lower():
- f = item.get('href').split('/')[-1].decode('utf-8')
+ f = item.get('href').split('/')[-1]
+ if isinstance(f, str):
+ f = f.decode('utf-8')
html_files.append(os.path.abspath(content(f)))
for path in html_files:
From bc4b6fdf486b48a2bf771f57025bd3cd2795176f Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 10:29:09 -0800
Subject: [PATCH 05/65] New recipe for Pobjeda by Darko Miletic
---
src/calibre/gui2/__init__.py | 39 ++-
src/calibre/gui2/add.py | 224 ++++++++++++++++++
src/calibre/gui2/dialogs/confirm_delete.py | 3 +-
src/calibre/gui2/dialogs/progress.py | 1 +
src/calibre/gui2/images/news/pobjeda.png | Bin 0 -> 285 bytes
src/calibre/gui2/main.py | 145 +++++-------
src/calibre/library/database.py | 114 +--------
src/calibre/library/database2.py | 112 ++++++++-
src/calibre/web/feeds/recipes/__init__.py | 1 +
.../web/feeds/recipes/recipe_pobjeda.py | 102 ++++++++
10 files changed, 532 insertions(+), 209 deletions(-)
create mode 100644 src/calibre/gui2/add.py
create mode 100644 src/calibre/gui2/images/news/pobjeda.png
create mode 100644 src/calibre/web/feeds/recipes/recipe_pobjeda.py
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 084b352f48..c33036e183 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -1,7 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
""" The GUI """
-import sys, os, re, StringIO, traceback
+import sys, os, re, StringIO, traceback, time
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication, \
QModelIndex
@@ -14,6 +14,9 @@
from calibre.startup import get_lang
from calibre.utils.config import Config, ConfigProxy, dynamic
import calibre.resources as resources
+from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
+from calibre.ebooks.metadata import MetaInformation
+
NONE = QVariant() #: Null value to return from the data function of item models
@@ -148,7 +151,41 @@ def __call__(self, *args, **kwargs):
def dispatch(self, args, kwargs):
self.func(*args, **kwargs)
+
+class GetMetadata(QObject):
+ '''
+ Convenience class to ensure that metadata readers are used only in the
+ GUI thread. Must be instantiated in the GUI thread.
+ '''
+
+ def __init__(self):
+ QObject.__init__(self)
+ self.connect(self, SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
+ self._get_metadata, Qt.QueuedConnection)
+ self.connect(self, SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
+ self._from_formats, Qt.QueuedConnection)
+ def __call__(self, id, *args, **kwargs):
+ self.emit(SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
+ id, args, kwargs)
+
+ def from_formats(self, id, *args, **kwargs):
+ self.emit(SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
+ id, args, kwargs)
+
+ def _from_formats(self, id, args, kwargs):
+ try:
+ mi = metadata_from_formats(*args, **kwargs)
+ except:
+ mi = MetaInformation('', [_('Unknown')])
+ self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi)
+
+ def _get_metadata(self, id, args, kwargs):
+ try:
+ mi = get_metadata(*args, **kwargs)
+ except:
+ mi = MetaInformation('', [_('Unknown')])
+ self.emit(SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), id, mi)
class TableView(QTableView):
def __init__(self, parent):
diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
new file mode 100644
index 0000000000..a6c6c699e8
--- /dev/null
+++ b/src/calibre/gui2/add.py
@@ -0,0 +1,224 @@
+'''
+UI for adding books to the database
+'''
+import os
+
+from PyQt4.Qt import QThread, SIGNAL, QMutex, QWaitCondition, Qt
+
+from calibre.gui2.dialogs.progress import ProgressDialog
+from calibre.constants import preferred_encoding
+from calibre.gui2.widgets import WarningDialog
+
+class Add(QThread):
+
+ def __init__(self):
+ QThread.__init__(self)
+ self._lock = QMutex()
+ self._waiting = QWaitCondition()
+
+ def is_canceled(self):
+ if self.pd.canceled:
+ self.canceled = True
+ return self.canceled
+
+ def wait_for_condition(self):
+ self._lock.lock()
+ self._waiting.wait(self._lock)
+ self._lock.unlock()
+
+ def wake_up(self):
+ self._waiting.wakeAll()
+
+class AddFiles(Add):
+
+ def __init__(self, paths, default_thumbnail, get_metadata, db=None):
+ Add.__init__(self)
+ self.paths = paths
+ self.get_metadata = get_metadata
+ self.default_thumbnail = default_thumbnail
+ self.db = db
+ self.formats, self.metadata, self.names, self.infos = [], [], [], []
+ self.duplicates = []
+ self.number_of_books_added = 0
+ self.connect(self.get_metadata,
+ SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'),
+ self.metadata_delivered)
+
+ def metadata_delivered(self, id, mi):
+ if self.is_canceled():
+ self.reading.wakeAll()
+ return
+ if not mi.title:
+ mi.title = os.path.splitext(self.names[id])[0]
+ mi.title = mi.title if isinstance(mi.title, unicode) else \
+ mi.title.decode(preferred_encoding, 'replace')
+ self.metadata.append(mi)
+ self.infos.append({'title':mi.title,
+ 'authors':', '.join(mi.authors),
+ 'cover':self.default_thumbnail, 'tags':[]})
+ if self.db is not None:
+ duplicates, num = self.db.add_books(self.paths[id:id+1],
+ self.formats[id:id+1], [mi],
+ add_duplicates=False)
+ self.number_of_books_added += num
+ if duplicates:
+ if not self.duplicates:
+ self.duplicates = [[], [], [], []]
+ for i in range(4):
+ self.duplicates[i] += duplicates[i]
+ self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
+ mi.title, id)
+ self.wake_up()
+
+ def create_progress_dialog(self, title, msg, parent):
+ self._parent = parent
+ self.pd = ProgressDialog(title, msg, -1, len(self.paths)-1, parent)
+ self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
+ self.update_progress_dialog)
+ self.pd.setModal(True)
+ self.pd.show()
+ self.connect(self, SIGNAL('finished()'), self.pd.hide)
+ return self.pd
+
+
+ def update_progress_dialog(self, title, count):
+ self.pd.set_value(count)
+ if self.db is not None:
+ self.pd.set_msg(_('Added %s to library')%title)
+ else:
+ self.pd.set_msg(_('Read metadata from ')+title)
+
+
+ def run(self):
+ self.canceled = False
+ for c, book in enumerate(self.paths):
+ if self.pd.canceled:
+ self.canceled = True
+ break
+ format = os.path.splitext(book)[1]
+ format = format[1:] if format else None
+ stream = open(book, 'rb')
+ self.formats.append(format)
+ self.names.append(os.path.basename(book))
+ self.get_metadata(c, stream, stream_type=format,
+ use_libprs_metadata=True)
+ self.wait_for_condition()
+
+
+ def process_duplicates(self):
+ if self.duplicates:
+ files = _('
Books with the same title as the following already '
+ 'exist in the database. Add them anyway?
')
+ for mi in self.duplicates[2]:
+ files += '- '+mi.title+'
\n'
+ d = WarningDialog (_('Duplicates found!'),
+ _('Duplicates found!'),
+ files+'
', parent=self._parent)
+ if d.exec_() == d.Accepted:
+ num = self.db.add_books(*self.duplicates,
+ **dict(add_duplicates=True))[1]
+ self.number_of_books_added += num
+
+
+class AddRecursive(Add):
+
+ def __init__(self, path, db, get_metadata, single_book_per_directory, parent):
+ self.path = path
+ self.db = db
+ self.get_metadata = get_metadata
+ self.single_book_per_directory = single_book_per_directory
+ self.duplicates, self.books, self.metadata = [], [], []
+ self.number_of_books_added = 0
+ self.canceled = False
+ Add.__init__(self)
+ self.connect(self.get_metadata,
+ SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'),
+ self.metadata_delivered, Qt.QueuedConnection)
+ self.connect(self, SIGNAL('searching_done()'), self.searching_done,
+ Qt.QueuedConnection)
+ self._parent = parent
+ self.pd = ProgressDialog(_('Adding books recursively...'),
+ _('Searching for books in all sub-directories...'),
+ 0, 0, parent)
+ self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
+ self.update_progress_dialog)
+ self.connect(self, SIGNAL('update(PyQt_PyObject)'), self.pd.set_msg,
+ Qt.QueuedConnection)
+ self.connect(self, SIGNAL('pupdate(PyQt_PyObject)'), self.pd.set_value,
+ Qt.QueuedConnection)
+ self.pd.setModal(True)
+ self.pd.show()
+ self.connect(self, SIGNAL('finished()'), self.pd.hide)
+
+ def update_progress_dialog(self, title, count):
+ self.pd.set_value(count)
+ if title:
+ self.pd.set_msg(_('Read metadata from ')+title)
+
+ def metadata_delivered(self, id, mi):
+ if self.is_canceled():
+ self.reading.wakeAll()
+ return
+ self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'),
+ mi.title, id)
+ self.metadata.append((mi if mi.title else None, self.books[id]))
+ if len(self.metadata) >= len(self.books):
+ self.metadata = [x for x in self.metadata if x[0] is not None]
+ self.pd.set_min(-1)
+ self.pd.set_max(len(self.metadata)-1)
+ self.pd.set_value(-1)
+ self.pd.set_msg(_('Adding books to database...'))
+ self.wake_up()
+
+ def searching_done(self):
+ self.pd.set_min(-1)
+ self.pd.set_max(len(self.books)-1)
+ self.pd.set_value(-1)
+ self.pd.set_msg(_('Reading metadata...'))
+
+
+ def run(self):
+ root = os.path.abspath(self.path)
+ for dirpath in os.walk(root):
+ if self.is_canceled():
+ return
+ self.emit(SIGNAL('update(PyQt_PyObject)'),
+ _('Searching in')+' '+dirpath[0])
+ self.books += list(self.db.find_books_in_directory(dirpath[0],
+ self.single_book_per_directory))
+ self.books = [formats for formats in self.books if formats]
+ # Reset progress bar
+ self.emit(SIGNAL('searching_done()'))
+
+ for c, formats in enumerate(self.books):
+ self.get_metadata.from_formats(c, formats)
+ self.wait_for_condition()
+
+ # Add books to database
+ for c, x in enumerate(self.metadata):
+ mi, formats = x
+ if self.is_canceled():
+ break
+ if self.db.has_book(mi):
+ self.duplicates.append((mi, formats))
+ else:
+ self.db.import_book(mi, formats, notify=False)
+ self.number_of_books_added += 1
+ self.emit(SIGNAL('pupdate(PyQt_PyObject)'), c)
+
+
+ def process_duplicates(self):
+ if self.duplicates:
+ files = _('Books with the same title as the following already '
+ 'exist in the database. Add them anyway?
')
+ for mi in self.duplicates:
+ files += '- '+mi[0].title+'
\n'
+ d = WarningDialog (_('Duplicates found!'),
+ _('Duplicates found!'),
+ files+'
', parent=self._parent)
+ if d.exec_() == d.Accepted:
+ for mi, formats in self.duplicates:
+ self.db.import_book(mi, formats, notify=False)
+ self.number_of_books_added += 1
+
+
\ No newline at end of file
diff --git a/src/calibre/gui2/dialogs/confirm_delete.py b/src/calibre/gui2/dialogs/confirm_delete.py
index 08db53e9a7..8c496987fb 100644
--- a/src/calibre/gui2/dialogs/confirm_delete.py
+++ b/src/calibre/gui2/dialogs/confirm_delete.py
@@ -5,7 +5,7 @@
from calibre.gui2 import dynamic
from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog
-from PyQt4.Qt import QDialog, SIGNAL
+from PyQt4.Qt import QDialog, SIGNAL, Qt
def _config_name(name):
return name + '_again'
@@ -19,6 +19,7 @@ def __init__(self, msg, name, parent):
self.msg.setText(msg)
self.name = name
self.connect(self.again, SIGNAL('stateChanged(int)'), self.toggle)
+ self.buttonBox.setFocus(Qt.OtherFocusReason)
def toggle(self, x):
diff --git a/src/calibre/gui2/dialogs/progress.py b/src/calibre/gui2/dialogs/progress.py
index 2543cefb4d..0f64d7b041 100644
--- a/src/calibre/gui2/dialogs/progress.py
+++ b/src/calibre/gui2/dialogs/progress.py
@@ -20,6 +20,7 @@ def __init__(self, title, msg='', min=0, max=99, parent=None):
self.setWindowModality(Qt.ApplicationModal)
self.set_min(min)
self.set_max(max)
+ self.bar.setValue(min)
self.canceled = False
self.connect(self.button_box, SIGNAL('rejected()'), self._canceled)
diff --git a/src/calibre/gui2/images/news/pobjeda.png b/src/calibre/gui2/images/news/pobjeda.png
new file mode 100644
index 0000000000000000000000000000000000000000..d7612b4e9ef1866298a80491456c439b1f36d225
GIT binary patch
literal 285
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b
zK-vS0-A-oPfdtD69Mgd`SU*F|v9*VR95+uF#}JFt$uf6Z@XI>3~C!+wYBq+`uo
zSv(-%Gv$V#D6_=G!wfg24b+7uNmuCiNICd2UzluUBtQSof$GMIm)`I&Y`redYv0m(
z1!$sbiEBhja#3nxNvduNkYF$}FtF4$Fw!+N2r;yBooks with the same title as the following already exist in the database. Add them anyway?')
- for mi, formats in duplicates:
- files += '- '+mi.title+'
\n'
- d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'),
- files+'
', self)
- if d.exec_() == QDialog.Accepted:
- for mi, formats in duplicates:
- self.library_view.model().db.import_book(mi, formats )
-
- self.library_view.model().resort()
- self.library_view.model().research()
-
+ from calibre.gui2.add import AddRecursive
+ self._add_recursive_thread = AddRecursive(root,
+ self.library_view.model().db, self.get_metadata,
+ single, self)
+ self.connect(self._add_recursive_thread, SIGNAL('finished()'),
+ self._recursive_files_added)
+ self._add_recursive_thread.start()
+
+ def _recursive_files_added(self):
+ self._add_recursive_thread.process_duplicates()
+ if self._add_recursive_thread.number_of_books_added > 0:
+ self.library_view.model().resort(reset=False)
+ self.library_view.model().research()
+ self.library_view.model().count_changed()
+ self._add_recursive_thread = None
+
def add_recursive_single(self, checked):
'''
Add books from the local filesystem to either the library or the device
@@ -686,66 +676,41 @@ def add_books(self, checked):
return
to_device = self.stack.currentIndex() != 0
self._add_books(books, to_device)
- if to_device:
- self.status_bar.showMessage(_('Uploading books to device.'), 2000)
+
def _add_books(self, paths, to_device, on_card=None):
if on_card is None:
on_card = self.stack.currentIndex() == 2
if not paths:
return
- # Get format and metadata information
- formats, metadata, names, infos = [], [], [], []
- progress = ProgressDialog(_('Adding books...'), _('Reading metadata...'),
- min=0, max=len(paths), parent=self)
- progress.show()
- try:
- for c, book in enumerate(paths):
- progress.set_value(c+1)
- if progress.canceled:
- return
- format = os.path.splitext(book)[1]
- format = format[1:] if format else None
- stream = open(book, 'rb')
- try:
- mi = get_metadata(stream, stream_type=format, use_libprs_metadata=True)
- except:
- mi = MetaInformation(None, None)
- if not mi.title:
- mi.title = os.path.splitext(os.path.basename(book))[0]
- if not mi.authors:
- mi.authors = [_('Unknown')]
- formats.append(format)
- metadata.append(mi)
- names.append(os.path.basename(book))
- infos.append({'title':mi.title, 'authors':', '.join(mi.authors),
- 'cover':self.default_thumbnail, 'tags':[]})
- title = mi.title if isinstance(mi.title, unicode) else mi.title.decode(preferred_encoding, 'replace')
- progress.set_msg(_('Read metadata from ')+title)
- QApplication.processEvents()
-
- if not to_device:
- progress.set_msg(_('Adding books to database...'))
- QApplication.processEvents()
- model = self.library_view.model()
-
- paths = list(paths)
- duplicates, number_added = model.add_books(paths, formats, metadata)
- if duplicates:
- files = _('Books with the same title as the following already exist in the database. Add them anyway?
')
- for mi in duplicates[2]:
- files += '- '+mi.title+'
\n'
- d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'), files+'
', parent=self)
- if d.exec_() == QDialog.Accepted:
- num = model.add_books(*duplicates, **dict(add_duplicates=True))[1]
- number_added += num
- model.books_added(number_added)
+ from calibre.gui2.add import AddFiles
+ self._add_files_thread = AddFiles(paths, self.default_thumbnail,
+ self.get_metadata,
+ None if to_device else \
+ self.library_view.model().db
+ )
+ self._add_files_thread.send_to_device = to_device
+ self._add_files_thread.on_card = on_card
+ self._add_files_thread.create_progress_dialog(_('Adding books...'),
+ _('Reading metadata...'), self)
+ self.connect(self._add_files_thread, SIGNAL('finished()'),
+ self._files_added)
+ self._add_files_thread.start()
+
+ def _files_added(self):
+ t = self._add_files_thread
+ self._add_files_thread = None
+ if not t.canceled:
+ if t.send_to_device:
+ self.upload_books(t.paths,
+ list(map(sanitize_file_name, t.names)),
+ t.infos, on_card=t.on_card)
+ self.status_bar.showMessage(_('Uploading books to device.'), 2000)
else:
- self.upload_books(paths, list(map(sanitize_file_name, names)), infos, on_card=on_card)
- finally:
- QApplication.processEvents()
- progress.hide()
-
+ t.process_duplicates()
+ if t.number_of_books_added > 0:
+ self.library_view.model().books_added(t.number_of_books_added)
+
def upload_books(self, files, names, metadata, on_card=False, memory=None):
'''
Upload books to device.
@@ -801,7 +766,10 @@ def delete_books(self, checked):
if not rows or len(rows) == 0:
return
if self.stack.currentIndex() == 0:
- if not confirm(''+_('The selected books will be permanently deleted and the files removed from your computer. Are you sure?')+'
', 'library_delete_books', self):
+ if not confirm(''+_('The selected books will be '
+ 'permanently deleted and the files '
+ 'removed from your computer. Are you sure?')
+ +'
', 'library_delete_books', self):
return
view.model().delete_books(rows)
else:
@@ -1410,8 +1378,15 @@ def job_exception(self, job):
def initialize_database(self):
self.library_path = prefs['library_path']
if self.library_path is None: # Need to migrate to new database layout
+ base = os.path.expanduser('~')
+ if iswindows:
+ from calibre import plugins
+ from PyQt4.Qt import QDir
+ base = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_PERSONAL)
+ if not base or not os.path.exists(base):
+ base = unicode(QDir.homePath()).replace('/', os.sep)
dir = unicode(QFileDialog.getExistingDirectory(self,
- _('Choose a location for your ebook library.'), os.getcwd()))
+ _('Choose a location for your ebook library.'), base))
if not dir:
dir = os.path.expanduser('~/Library')
self.library_path = os.path.abspath(dir)
diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py
index 37fdeb4ce4..e4fa71feed 100644
--- a/src/calibre/library/database.py
+++ b/src/calibre/library/database.py
@@ -4,13 +4,10 @@
Backend that implements storage of ebooks in an sqlite database.
'''
import sqlite3 as sqlite
-import datetime, re, os, cPickle, sre_constants
+import datetime, re, cPickle, sre_constants
from zlib import compress, decompress
-from calibre import sanitize_file_name
-from calibre.ebooks.metadata.meta import set_metadata, metadata_from_formats
from calibre.ebooks.metadata import MetaInformation
-from calibre.ebooks import BOOK_EXTENSIONS
from calibre.web.feeds.recipes import migrate_automatic_profile_to_automatic_recipe
class Concatenate(object):
@@ -1391,117 +1388,12 @@ def all_ids(self):
- def import_book(self, mi, formats):
- series_index = 1 if mi.series_index is None else mi.series_index
- if not mi.authors:
- mi.authors = [_('Unknown')]
- aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
- obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
- (mi.title, None, series_index, aus))
- id = obj.lastrowid
- self.conn.commit()
- self.set_metadata(id, mi)
- for path in formats:
- ext = os.path.splitext(path)[1][1:].lower()
- stream = open(path, 'rb')
- stream.seek(0, 2)
- usize = stream.tell()
- stream.seek(0)
- data = sqlite.Binary(compress(stream.read()))
- try:
- self.conn.execute('INSERT INTO data(book, format, uncompressed_size, data) VALUES (?,?,?,?)',
- (id, ext, usize, data))
- except sqlite.IntegrityError:
- self.conn.execute('UPDATE data SET uncompressed_size=?, data=? WHERE book=? AND format=?',
- (usize, data, id, ext))
- self.conn.commit()
-
- def import_book_directory_multiple(self, dirpath, callback=None):
- dirpath = os.path.abspath(dirpath)
- duplicates = []
- books = {}
- for path in os.listdir(dirpath):
- if callable(callback):
- callback('.')
- path = os.path.abspath(os.path.join(dirpath, path))
- if os.path.isdir(path) or not os.access(path, os.R_OK):
- continue
- ext = os.path.splitext(path)[1]
- if not ext:
- continue
- ext = ext[1:].lower()
- if ext not in BOOK_EXTENSIONS:
- continue
-
- key = os.path.splitext(path)[0]
- if not books.has_key(key):
- books[key] = []
-
- books[key].append(path)
-
- for formats in books.values():
- mi = metadata_from_formats(formats)
- if mi.title is None:
- continue
- if self.has_book(mi):
- duplicates.append((mi, formats))
- continue
- self.import_book(mi, formats)
- if callable(callback):
- if callback(mi.title):
- break
- return duplicates
-
-
- def import_book_directory(self, dirpath, callback=None):
- dirpath = os.path.abspath(dirpath)
- formats = []
- for path in os.listdir(dirpath):
- if callable(callback):
- callback('.')
- path = os.path.abspath(os.path.join(dirpath, path))
- if os.path.isdir(path) or not os.access(path, os.R_OK):
- continue
- ext = os.path.splitext(path)[1]
- if not ext:
- continue
- ext = ext[1:].lower()
- if ext not in BOOK_EXTENSIONS:
- continue
- formats.append(path)
-
- if not formats:
- return
-
- mi = metadata_from_formats(formats)
- if mi.title is None:
- return
- if self.has_book(mi):
- return [(mi, formats)]
- self.import_book(mi, formats)
- if callable(callback):
- callback(mi.title)
-
-
+
def has_id(self, id):
return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None
- def recursive_import(self, root, single_book_per_directory=True, callback=None):
- root = os.path.abspath(root)
- duplicates = []
- for dirpath in os.walk(root):
- res = self.import_book_directory(dirpath[0], callback=callback) if \
- single_book_per_directory else \
- self.import_book_directory_multiple(dirpath[0], callback=callback)
- if res is not None:
- duplicates.extend(res)
- if callable(callback):
- if callback(''):
- break
-
- return duplicates
-
+
class SearchToken(object):
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 8c762f8680..a7f522cad0 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -12,7 +12,7 @@
from datetime import datetime
from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock
-from PyQt4.QtGui import QApplication, QPixmap, QImage
+from PyQt4.QtGui import QApplication, QImage
__app = None
from calibre.library import title_sort
@@ -20,12 +20,15 @@
from calibre.library.sqlite import connect, IntegrityError
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.ebooks.metadata import string_to_authors, authors_to_string, MetaInformation
-from calibre.ebooks.metadata.meta import get_metadata, set_metadata
+from calibre.ebooks.metadata.meta import get_metadata, set_metadata, \
+ metadata_from_formats
from calibre.ebooks.metadata.opf2 import OPFCreator
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
from calibre.ptempfile import PersistentTemporaryFile
from calibre.customize.ui import run_plugins_on_import
+
from calibre import sanitize_file_name
+from calibre.ebooks import BOOK_EXTENSIONS
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
@@ -627,7 +630,7 @@ def set_cover(self, id, data):
if not QCoreApplication.instance():
global __app
__app = QApplication([])
- p = QPixmap()
+ p = QImage()
if callable(getattr(data, 'read', None)):
data = data.read()
p.loadFromData(data)
@@ -1142,7 +1145,7 @@ def run_import_plugins(self, path_or_stream, format):
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
'''
Add a book to the database. The result cache is not updated.
- @param paths: List of paths to book files or file-like objects
+ :param:`paths` List of paths to book files or file-like objects
'''
formats, metadata, uris = iter(formats), iter(metadata), iter(uris)
duplicates = []
@@ -1180,17 +1183,17 @@ def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
self.conn.commit()
self.data.refresh_ids(self.conn, ids) # Needed to update format list and size
if duplicates:
- paths = tuple(duplicate[0] for duplicate in duplicates)
- formats = tuple(duplicate[1] for duplicate in duplicates)
- metadata = tuple(duplicate[2] for duplicate in duplicates)
- uris = tuple(duplicate[3] for duplicate in duplicates)
+ paths = list(duplicate[0] for duplicate in duplicates)
+ formats = list(duplicate[1] for duplicate in duplicates)
+ metadata = list(duplicate[2] for duplicate in duplicates)
+ uris = list(duplicate[3] for duplicate in duplicates)
return (paths, formats, metadata, uris), len(ids)
return None, len(ids)
- def import_book(self, mi, formats):
+ def import_book(self, mi, formats, notify=True):
series_index = 1 if mi.series_index is None else mi.series_index
if not mi.authors:
- mi.authors = ['Unknown']
+ mi.authors = [_('Unknown')]
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
(mi.title, None, series_index, aus))
@@ -1204,7 +1207,8 @@ def import_book(self, mi, formats):
self.add_format(id, ext, stream, index_is_id=True)
self.conn.commit()
self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size
- self.notify('add', [id])
+ if notify:
+ self.notify('add', [id])
def move_library_to(self, newloc, progress=None):
header = _(u'Copying books to %s
')%newloc
@@ -1388,6 +1392,10 @@ def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb')
if not mi.authors:
mi.authors = [_('Unknown')]
+ cdata = self.cover(id, index_is_id=True)
+ cname = sanitize_file_name(name)+'.jpg'
+ open(os.path.join(base, cname), 'wb').write(cdata)
+ mi.cover = cname
opf = OPFCreator(base, mi)
opf.render(f)
f.close()
@@ -1451,6 +1459,88 @@ def export_single_format_to_dir(self, dir, indices, format,
if not callback(count, title):
break
return failures
+
+ def find_books_in_directory(self, dirpath, single_book_per_directory):
+ dirpath = os.path.abspath(dirpath)
+ if single_book_per_directory:
+ formats = []
+ for path in os.listdir(dirpath):
+ path = os.path.abspath(os.path.join(dirpath, path))
+ if os.path.isdir(path) or not os.access(path, os.R_OK):
+ continue
+ ext = os.path.splitext(path)[1]
+ if not ext:
+ continue
+ ext = ext[1:].lower()
+ if ext not in BOOK_EXTENSIONS:
+ continue
+ formats.append(path)
+ yield formats
+ else:
+ books = {}
+ for path in os.listdir(dirpath):
+ path = os.path.abspath(os.path.join(dirpath, path))
+ if os.path.isdir(path) or not os.access(path, os.R_OK):
+ continue
+ ext = os.path.splitext(path)[1]
+ if not ext:
+ continue
+ ext = ext[1:].lower()
+ if ext not in BOOK_EXTENSIONS:
+ continue
+
+ key = os.path.splitext(path)[0]
+ if not books.has_key(key):
+ books[key] = []
+ books[key].append(path)
+
+ for formats in books.values():
+ yield formats
+
+ def import_book_directory_multiple(self, dirpath, callback=None):
+ duplicates = []
+ for formats in self.find_books_in_directory(dirpath, False):
+ mi = metadata_from_formats(formats)
+ if mi.title is None:
+ continue
+ if self.has_book(mi):
+ duplicates.append((mi, formats))
+ continue
+ self.import_book(mi, formats)
+ if callable(callback):
+ if callback(mi.title):
+ break
+ return duplicates
+
+ def import_book_directory(self, dirpath, callback=None):
+ dirpath = os.path.abspath(dirpath)
+ formats = self.find_books_in_directory(dirpath, True)
+ if not formats:
+ return
+
+ mi = metadata_from_formats(formats)
+ if mi.title is None:
+ return
+ if self.has_book(mi):
+ return [(mi, formats)]
+ self.import_book(mi, formats)
+ if callable(callback):
+ callback(mi.title)
+
+ def recursive_import(self, root, single_book_per_directory=True, callback=None):
+ root = os.path.abspath(root)
+ duplicates = []
+ for dirpath in os.walk(root):
+ res = self.import_book_directory(dirpath[0], callback=callback) if \
+ single_book_per_directory else \
+ self.import_book_directory_multiple(dirpath[0], callback=callback)
+ if res is not None:
+ duplicates.extend(res)
+ if callable(callback):
+ if callback(''):
+ break
+
+ return duplicates
diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py
index 60ae0761cf..dcbec14687 100644
--- a/src/calibre/web/feeds/recipes/__init__.py
+++ b/src/calibre/web/feeds/recipes/__init__.py
@@ -28,6 +28,7 @@
'la_tercera', 'el_mercurio_chile', 'la_cuarta', 'lanacion_chile', 'la_segunda',
'jb_online', 'estadao', 'o_globo', 'vijesti', 'elmundo', 'the_oz',
'honoluluadvertiser', 'starbulletin', 'exiled', 'indy_star', 'dna',
+ 'pobjeda',
)]
import re, imp, inspect, time, os
diff --git a/src/calibre/web/feeds/recipes/recipe_pobjeda.py b/src/calibre/web/feeds/recipes/recipe_pobjeda.py
new file mode 100644
index 0000000000..9a4dbb0eee
--- /dev/null
+++ b/src/calibre/web/feeds/recipes/recipe_pobjeda.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+__license__ = 'GPL v3'
+__copyright__ = '2009, Darko Miletic '
+
+'''
+pobjeda.co.me
+'''
+
+import re
+from calibre import strftime
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class Pobjeda(BasicNewsRecipe):
+ title = 'Pobjeda Online'
+ __author__ = 'Darko Miletic'
+ description = 'News from Montenegro'
+ publisher = 'Pobjeda a.d.'
+ category = 'news, politics, Montenegro'
+ language = _('Serbian')
+ oldest_article = 2
+ max_articles_per_feed = 100
+ no_stylesheets = True
+ remove_javascript = True
+ encoding = 'utf8'
+ remove_javascript = True
+ use_embedded_content = False
+ INDEX = u'http://www.pobjeda.co.me'
+ extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{text-align: justify; font-family: serif1, serif} .article_description{font-family: serif1, serif}'
+
+ html2lrf_options = [
+ '--comment', description
+ , '--category', category
+ , '--publisher', publisher
+ ]
+
+ html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
+
+ preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
+
+ keep_only_tags = [dict(name='div', attrs={'class':'vijest'})]
+
+ remove_tags = [dict(name=['object','link'])]
+
+ feeds = [
+ (u'Politika' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=1' )
+ ,(u'Ekonomija' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=2' )
+ ,(u'Drustvo' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=3' )
+ ,(u'Crna Hronika' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=4' )
+ ,(u'Kultura' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=5' )
+ ,(u'Hronika Podgorice' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=7' )
+ ,(u'Feljton' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=8' )
+ ,(u'Crna Gora' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=9' )
+ ,(u'Svijet' , u'http://www.pobjeda.co.me/rubrika.php?rubrika=202')
+ ,(u'Ekonomija i Biznis', u'http://www.pobjeda.co.me/dodatak.php?rubrika=11' )
+ ,(u'Djeciji Svijet' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=12' )
+ ,(u'Kultura i Drustvo' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=13' )
+ ,(u'Agora' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=133')
+ ,(u'Ekologija' , u'http://www.pobjeda.co.me/dodatak.php?rubrika=252')
+ ]
+
+ def preprocess_html(self, soup):
+ soup.html['xml:lang'] = 'sr-Latn-ME'
+ soup.html['lang'] = 'sr-Latn-ME'
+ mtag = ''
+ soup.head.insert(0,mtag)
+ for item in soup.findAll(style=True):
+ del item['style']
+ return soup
+
+ def get_cover_url(self):
+ cover_url = None
+ soup = self.index_to_soup(self.INDEX)
+ cover_item = soup.find('img',attrs={'alt':'Naslovna strana'})
+ if cover_item:
+ cover_url = self.INDEX + cover_item.parent['href']
+ return cover_url
+
+ def parse_index(self):
+ totalfeeds = []
+ lfeeds = self.get_feeds()
+ for feedobj in lfeeds:
+ feedtitle, feedurl = feedobj
+ self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
+ articles = []
+ soup = self.index_to_soup(feedurl)
+ for item in soup.findAll('div', attrs={'class':'vijest'}):
+ description = self.tag_to_string(item.h2)
+ atag = item.h1.find('a')
+ if atag:
+ url = self.INDEX + '/' + atag['href']
+ title = self.tag_to_string(atag)
+ date = strftime(self.timefmt)
+ articles.append({
+ 'title' :title
+ ,'date' :date
+ ,'url' :url
+ ,'description':description
+ })
+ totalfeeds.append((feedtitle, articles))
+ return totalfeeds
+
From 83bc5c809690eaa45a2e6217dd554cf2f5ccdda7 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 10:40:47 -0800
Subject: [PATCH 06/65] PRS505/700: Dont fail to detect the device if SD card
is buggy
---
src/calibre/devices/prs505/driver.py | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py
index 9308af2c5a..fb8bbf5378 100644
--- a/src/calibre/devices/prs505/driver.py
+++ b/src/calibre/devices/prs505/driver.py
@@ -248,15 +248,20 @@ def open(self):
time.sleep(3)
self.open_osx()
if self._card_prefix is not None:
- cachep = os.path.join(self._card_prefix, self.CACHE_XML)
- if not os.path.exists(cachep):
- os.makedirs(os.path.dirname(cachep), mode=0777)
- f = open(cachep, 'wb')
- f.write(u'''
+ try:
+ cachep = os.path.join(self._card_prefix, self.CACHE_XML)
+ if not os.path.exists(cachep):
+ os.makedirs(os.path.dirname(cachep), mode=0777)
+ f = open(cachep, 'wb')
+ f.write(u'''
'''.encode('utf8'))
- f.close()
+ f.close()
+ except:
+ self._card_prefix = None
+ import traceback
+ traceback.print_exc()
def set_progress_reporter(self, pr):
self.report_progress = pr
From 1746aeafe00750113dbce50682010457ad15041e Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 13:01:17 -0800
Subject: [PATCH 07/65] When saving to disk, also save the date. When importing
from disk (ebook per directory), if an OPF file is present, use that to read
metadata, including date. Adds new dependency on python-dateutil. Rewrite the
Add books code for greateer speed and also fix the bug causing the Add books
progress dialog to sometimes not close on windows.
---
installer/linux/freeze.py | 3 ++-
installer/osx/freeze.py | 1 +
installer/windows/freeze.py | 3 ++-
src/calibre/ebooks/metadata/__init__.py | 14 +++++++++-----
src/calibre/ebooks/metadata/meta.py | 10 ++++++++--
src/calibre/ebooks/metadata/opf.xml | 2 +-
src/calibre/ebooks/metadata/opf2.py | 2 ++
src/calibre/library/database2.py | 22 +++++++++++++++-------
src/calibre/trac/plugins/download.py | 1 +
9 files changed, 41 insertions(+), 17 deletions(-)
diff --git a/installer/linux/freeze.py b/installer/linux/freeze.py
index c381041675..a6151c4931 100644
--- a/installer/linux/freeze.py
+++ b/installer/linux/freeze.py
@@ -81,7 +81,8 @@ def freeze():
'PyQt4.QtScript.so', 'PyQt4.QtSql.so', 'PyQt4.QtTest.so', 'qt',
'glib', 'gobject']
- packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg']
+ packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg',
+ 'dateutil']
includes += ['calibre.web.feeds.recipes.'+r for r in recipe_modules]
diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py
index 3ec24d3aba..dbaad72748 100644
--- a/installer/osx/freeze.py
+++ b/installer/osx/freeze.py
@@ -342,6 +342,7 @@ def main():
'calibre.ebooks.lrf.any.*', 'calibre.ebooks.lrf.feeds.*',
'keyword', 'codeop', 'pydoc', 'readline',
'BeautifulSoup', 'calibre.ebooks.lrf.fonts.prs500.*',
+ 'dateutil',
],
'packages' : ['PIL', 'Authorization', 'lxml'],
'excludes' : ['IPython'],
diff --git a/installer/windows/freeze.py b/installer/windows/freeze.py
index ab58fb669d..56486f6bd5 100644
--- a/installer/windows/freeze.py
+++ b/installer/windows/freeze.py
@@ -179,7 +179,8 @@ def main(args=sys.argv):
'calibre.ebooks.lrf.fonts.prs500.*',
'PyQt4.QtWebKit', 'PyQt4.QtNetwork',
],
- 'packages' : ['PIL', 'lxml', 'cherrypy'],
+ 'packages' : ['PIL', 'lxml', 'cherrypy',
+ 'dateutil'],
'excludes' : ["Tkconstants", "Tkinter", "tcl",
"_imagingtk", "ImageTk", "FixTk"
],
diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py
index eabd082142..d9b0514362 100644
--- a/src/calibre/ebooks/metadata/__init__.py
+++ b/src/calibre/ebooks/metadata/__init__.py
@@ -192,7 +192,8 @@ def copy(mi):
for attr in ('author_sort', 'title_sort', 'comments', 'category',
'publisher', 'series', 'series_index', 'rating',
'isbn', 'tags', 'cover_data', 'application_id', 'guide',
- 'manifest', 'spine', 'toc', 'cover', 'language', 'book_producer'):
+ 'manifest', 'spine', 'toc', 'cover', 'language',
+ 'book_producer', 'timestamp'):
if hasattr(mi, attr):
setattr(ans, attr, getattr(mi, attr))
@@ -217,7 +218,7 @@ def __init__(self, title, authors=[_('Unknown')]):
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher',
'series', 'series_index', 'rating', 'isbn', 'language',
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
- 'book_producer',
+ 'book_producer', 'timestamp'
):
setattr(self, x, getattr(mi, x, None))
@@ -235,7 +236,8 @@ def smart_update(self, mi):
for attr in ('author_sort', 'title_sort', 'comments', 'category',
'publisher', 'series', 'series_index', 'rating',
'isbn', 'application_id', 'manifest', 'spine', 'toc',
- 'cover', 'language', 'guide', 'book_producer'):
+ 'cover', 'language', 'guide', 'book_producer',
+ 'timestamp'):
if hasattr(mi, attr):
val = getattr(mi, attr)
if val is not None:
@@ -276,6 +278,8 @@ def __unicode__(self):
ans += u'Series : '+unicode(self.series) + ' #%s\n'%self.format_series_index()
if self.language:
ans += u'Language : ' + unicode(self.language) + u'\n'
+ if self.timestamp is not None:
+ ans += u'Timestamp : ' + self.timestamp.isoformat(' ')
return ans.strip()
def to_html(self):
@@ -289,12 +293,12 @@ def to_html(self):
if self.series:
ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]
ans += [(_('Language'), unicode(self.language))]
+ if self.timestamp is not None:
+ ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
for i, x in enumerate(ans):
ans[i] = u'| %s | %s |
'%x
return u''%u'\n'.join(ans)
-
-
def __str__(self):
return self.__unicode__().encode('utf-8')
diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py
index d8116b33d3..1241238f26 100644
--- a/src/calibre/ebooks/metadata/meta.py
+++ b/src/calibre/ebooks/metadata/meta.py
@@ -31,8 +31,14 @@ def metadata_from_formats(formats):
mi = MetaInformation(None, None)
formats.sort(cmp=lambda x,y: cmp(METADATA_PRIORITIES[path_to_ext(x)],
METADATA_PRIORITIES[path_to_ext(y)]))
- for path in formats:
- ext = path_to_ext(path)
+ extensions = list(map(path_to_ext, formats))
+ if 'opf' in extensions:
+ opf = formats[extensions.index('opf')]
+ mi2 = opf_metadata(opf)
+ if mi2 is not None and mi2.title:
+ return mi2
+
+ for path, ext in zip(formats, extensions):
stream = open(path, 'rb')
try:
mi.smart_update(get_metadata(stream, stream_type=ext, use_libprs_metadata=True))
diff --git a/src/calibre/ebooks/metadata/opf.xml b/src/calibre/ebooks/metadata/opf.xml
index 94a8f63b3c..9dab4efbf4 100644
--- a/src/calibre/ebooks/metadata/opf.xml
+++ b/src/calibre/ebooks/metadata/opf.xml
@@ -10,7 +10,7 @@
${author}
${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]
${mi.application_id}
-
+ ${mi.timestamp.isoformat()}
${mi.language if mi.language else 'UND'}
${mi.category}
${mi.comments}
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 32ba2cb45a..2e3b5ff047 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -12,6 +12,7 @@
from urlparse import urlparse
from lxml import etree
+from dateutil import parser
from calibre.ebooks.chardet import xml_to_unicode
from calibre import relpath
@@ -436,6 +437,7 @@ class OPF(object):
series = MetadataField('series', is_dc=False)
series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1)
rating = MetadataField('rating', is_dc=False, formatter=int)
+ timestamp = MetadataField('date', formatter=parser.parse)
def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True):
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index a7f522cad0..00da8f3e37 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -380,8 +380,10 @@ def get_property(idx, index_is_id=False, loc=-1):
return row[loc]
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
- 'publisher', 'rating', 'series', 'series_index', 'tags', 'title'):
- setattr(self, prop, functools.partial(get_property, loc=FIELD_MAP['comments' if prop == 'comment' else prop]))
+ 'publisher', 'rating', 'series', 'series_index', 'tags',
+ 'title', 'timestamp'):
+ setattr(self, prop, functools.partial(get_property,
+ loc=FIELD_MAP['comments' if prop == 'comment' else prop]))
def initialize_database(self):
from calibre.resources import metadata_sqlite
@@ -590,6 +592,7 @@ def get_metadata(self, idx, index_is_id=False, get_cover=False):
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
mi.comments = self.comments(idx, index_is_id=index_is_id)
mi.publisher = self.publisher(idx, index_is_id=index_is_id)
+ mi.timestamp = self.timestamp(idx, index_is_id=index_is_id)
tags = self.tags(idx, index_is_id=index_is_id)
if tags:
mi.tags = [i.strip() for i in tags.split(',')]
@@ -884,6 +887,8 @@ def set_metadata(self, id, mi):
self.set_isbn(id, mi.isbn, notify=False)
if mi.series_index and mi.series_index > 0:
self.set_series_index(id, mi.series_index, notify=False)
+ if getattr(mi, 'timestamp', None) is not None:
+ self.set_timestamp(id, mi.timestamp, notify=False)
self.set_path(id, True)
self.notify('metadata', [id])
@@ -1203,6 +1208,8 @@ def import_book(self, mi, formats, notify=True):
self.set_metadata(id, mi)
for path in formats:
ext = os.path.splitext(path)[1][1:].lower()
+ if ext == 'opf':
+ continue
stream = open(path, 'rb')
self.add_format(id, ext, stream, index_is_id=True)
self.conn.commit()
@@ -1392,10 +1399,11 @@ def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb')
if not mi.authors:
mi.authors = [_('Unknown')]
- cdata = self.cover(id, index_is_id=True)
- cname = sanitize_file_name(name)+'.jpg'
- open(os.path.join(base, cname), 'wb').write(cdata)
- mi.cover = cname
+ cdata = self.cover(int(id), index_is_id=True)
+ if cdata is not None:
+ cname = sanitize_file_name(name)+'.jpg'
+ open(os.path.join(base, cname), 'wb').write(cdata)
+ mi.cover = cname
opf = OPFCreator(base, mi)
opf.render(f)
f.close()
@@ -1472,7 +1480,7 @@ def find_books_in_directory(self, dirpath, single_book_per_directory):
if not ext:
continue
ext = ext[1:].lower()
- if ext not in BOOK_EXTENSIONS:
+ if ext not in BOOK_EXTENSIONS and ext != 'opf':
continue
formats.append(path)
yield formats
diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py
index 63bb4006e1..e63a7d8d0d 100644
--- a/src/calibre/trac/plugins/download.py
+++ b/src/calibre/trac/plugins/download.py
@@ -35,6 +35,7 @@ class Distribution(object):
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
('lxml', '2.0.5', 'lxml', 'python-lxml', 'python-lxml'),
+ ('python-dateutil', '1.4.1', 'python-dateutil', 'python-dateutil', 'python-dateutil')
('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'),
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
]
From 97a64f159fb993fe8f8c120400586819bd708640 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 13:38:10 -0800
Subject: [PATCH 08/65] IGN:Mark EPUB output as stable and commit temoprary fix
for #1817
---
src/calibre/ebooks/mobi/reader.py | 4 ++--
src/calibre/gui2/main.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py
index 6811f9ccda..8b81702bc1 100644
--- a/src/calibre/ebooks/mobi/reader.py
+++ b/src/calibre/ebooks/mobi/reader.py
@@ -308,8 +308,8 @@ def upshift_markup(self, root):
if 'filepos-id' in attrib:
attrib['id'] = attrib.pop('filepos-id')
if 'filepos' in attrib:
- filepos = int(attrib.pop('filepos'))
- attrib['href'] = "#filepos%d" % filepos
+ filepos = attrib.pop('filepos')
+ attrib['href'] = "#filepos%s" % filepos
if tag.tag == 'img':
recindex = None
for attr in self.IMAGE_ATTRS:
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index c2e755a742..0e59e35e1e 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -391,7 +391,7 @@ def do_default_sync(self, checked):
def change_output_format(self, x):
of = unicode(x).strip()
if of != prefs['output_format']:
- if of not in ('LRF',):
+ if of not in ('LRF', 'EPUB'):
warning_dialog(self, 'Warning',
'%s support is still in beta. If you find bugs, please report them by opening a ticket.'%of).exec_()
prefs.set('output_format', of)
From 4fa00105b39a1b802deafaf3c34ca803cddeee16 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 13:45:27 -0800
Subject: [PATCH 09/65] IGN:...
---
src/calibre/ebooks/mobi/reader.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py
index 8b81702bc1..1c434c9472 100644
--- a/src/calibre/ebooks/mobi/reader.py
+++ b/src/calibre/ebooks/mobi/reader.py
@@ -309,7 +309,10 @@ def upshift_markup(self, root):
attrib['id'] = attrib.pop('filepos-id')
if 'filepos' in attrib:
filepos = attrib.pop('filepos')
- attrib['href'] = "#filepos%s" % filepos
+ try:
+ attrib['href'] = "#filepos%d" % int(filepos)
+ except ValueError:
+ attrib['href'] = filepos
if tag.tag == 'img':
recindex = None
for attr in self.IMAGE_ATTRS:
From de1f54d39f7420e147c6cdc27309cd4570f2d24d Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 13:45:52 -0800
Subject: [PATCH 10/65] IGN:...
---
src/calibre/ebooks/mobi/reader.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py
index 1c434c9472..bfbe8f5ae5 100644
--- a/src/calibre/ebooks/mobi/reader.py
+++ b/src/calibre/ebooks/mobi/reader.py
@@ -311,7 +311,7 @@ def upshift_markup(self, root):
filepos = attrib.pop('filepos')
try:
attrib['href'] = "#filepos%d" % int(filepos)
- except ValueError:
+ except:
attrib['href'] = filepos
if tag.tag == 'img':
recindex = None
From 5c4294652ef219df1989df78d7089af09850d784 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 13:59:37 -0800
Subject: [PATCH 11/65] Fix system tray icon not being hidden when quitting on
windows
---
src/calibre/gui2/main.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index 0e59e35e1e..e674ca4fd6 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -1572,6 +1572,11 @@ def main(args=sys.argv):
print 'Restarting with:', e, sys.argv
os.execvp(e, sys.argv)
else:
+ if iswindows:
+ try:
+ main.system_tray_icon.hide()
+ except:
+ pass
return ret
return 0
From 005ca51c8441f729b09cc9373eaadb481db5dbff Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 14:23:29 -0800
Subject: [PATCH 12/65] Fix bug in handling of author names with commas when
sending books to device
---
src/calibre/gui2/library.py | 38 ++++++++++++++-----------------------
src/calibre/gui2/main.py | 1 -
2 files changed, 14 insertions(+), 25 deletions(-)
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index 9f0877ca09..199c4ada67 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -1,3 +1,4 @@
+from calibre.ebooks.metadata import authors_to_string
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
import os, textwrap, traceback, time, re
@@ -371,35 +372,24 @@ def get_metadata(self, rows, rows_are_ids=False):
if not rows_are_ids:
rows = [self.db.id(row.row()) for row in rows]
for id in rows:
- au = self.db.authors(id, index_is_id=True)
- tags = self.db.tags(id, index_is_id=True)
- if not au:
- au = _('Unknown')
- au = au.split(',')
- if len(au) > 1:
- t = ', '.join(au[:-1])
- t += ' & ' + au[-1]
- au = t
- else:
- au = ' & '.join(au)
- if not tags:
- tags = []
- else:
- tags = tags.split(',')
- series = self.db.series(id, index_is_id=True)
- if series is not None:
- tags.append(series)
- mi = {
- 'title' : self.db.title(id, index_is_id=True),
+ mi = self.db.get_metadata(id, index_is_id=True)
+ au = authors_to_string(mi.authors if mi.authors else [_('Unknown')])
+ tags = mi.tags if mi.tags else []
+ if mi.series is not None:
+ tags.append(mi.series)
+ info = {
+ 'title' : mi.title,
'authors' : au,
'cover' : self.db.cover(id, index_is_id=True),
'tags' : tags,
- 'comments': self.db.comments(id, index_is_id=True),
+ 'comments': mi.comments,
}
- if series is not None:
- mi['tag order'] = {series:self.db.books_in_series_of(id, index_is_id=True)}
+ if mi.series is not None:
+ info['tag order'] = {
+ mi.series:self.db.books_in_series_of(id, index_is_id=True)
+ }
- metadata.append(mi)
+ metadata.append(info)
return metadata
def get_preferred_formats_from_ids(self, ids, all_formats, mode='r+b'):
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index e674ca4fd6..83665ac8a7 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -48,7 +48,6 @@
from calibre.library.database2 import LibraryDatabase2, CoverCache
from calibre.parallel import JobKilled
from calibre.utils.filenames import ascii_filename
-from calibre.gui2.widgets import WarningDialog
from calibre.gui2.dialogs.confirm_delete import confirm
class Main(MainWindow, Ui_MainWindow):
From 9991549be6eb776766f47035a7e182cfb5b6b588 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 14:37:15 -0800
Subject: [PATCH 13/65] version 0.4.136
---
src/calibre/constants.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index cbb7fba14e..2d77964693 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -2,7 +2,7 @@
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
-__version__ = '0.4.135'
+__version__ = '0.4.136'
__author__ = "Kovid Goyal "
'''
Various run time constants.
From dd675d62102466c1b72c9facd27ec8a7b21e0aa8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 14:41:54 -0800
Subject: [PATCH 14/65] IGN:Tag release
From b18f95c02f013eed0bc6150b3dfe7a157547b1a8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 16:07:19 -0800
Subject: [PATCH 15/65] Fix #1805 (spurious ![endif]>![if> 's found in title
and chapter)
---
src/calibre/ebooks/html.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/html.py b/src/calibre/ebooks/html.py
index e264fec7cb..ad4538b302 100644
--- a/src/calibre/ebooks/html.py
+++ b/src/calibre/ebooks/html.py
@@ -330,7 +330,8 @@ class PreProcessor(object):
sanitize_head),
# Convert all entities, since lxml doesn't handle them well
(re.compile(r'&(\S+?);'), convert_entities),
-
+ # Remove ]*>'), lambda match: ''),
]
# Fix pdftohtml markup
From 4a172b34adc586bbf709e7ed6716c3a702236e39 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 16:09:27 -0800
Subject: [PATCH 16/65] IGN:...
---
src/calibre/ebooks/html.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/html.py b/src/calibre/ebooks/html.py
index ad4538b302..f69f26a1e6 100644
--- a/src/calibre/ebooks/html.py
+++ b/src/calibre/ebooks/html.py
@@ -330,7 +330,7 @@ class PreProcessor(object):
sanitize_head),
# Convert all entities, since lxml doesn't handle them well
(re.compile(r'&(\S+?);'), convert_entities),
- # Remove ]*>'), lambda match: ''),
]
From 5aa219339dc0a8697cff65009318fc27523133eb Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 16:14:48 -0800
Subject: [PATCH 17/65] IGN:...
---
src/calibre/trac/plugins/download.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py
index e63a7d8d0d..7abf7faed4 100644
--- a/src/calibre/trac/plugins/download.py
+++ b/src/calibre/trac/plugins/download.py
@@ -35,7 +35,7 @@ class Distribution(object):
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
('lxml', '2.0.5', 'lxml', 'python-lxml', 'python-lxml'),
- ('python-dateutil', '1.4.1', 'python-dateutil', 'python-dateutil', 'python-dateutil')
+ ('python-dateutil', '1.4.1', 'python-dateutil', 'python-dateutil', 'python-dateutil'),
('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'),
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
]
From 78e5a7d928e2324663dbc1110fc754797671dd96 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 10 Feb 2009 18:19:58 -0800
Subject: [PATCH 18/65] IGN:Mark politika recipe as Serbian
---
src/calibre/web/feeds/recipes/recipe_politika.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/calibre/web/feeds/recipes/recipe_politika.py b/src/calibre/web/feeds/recipes/recipe_politika.py
index 1575d8984f..f1d84915ce 100644
--- a/src/calibre/web/feeds/recipes/recipe_politika.py
+++ b/src/calibre/web/feeds/recipes/recipe_politika.py
@@ -16,6 +16,7 @@ class Politika(BasicNewsRecipe):
publisher = 'Politika novine i Magazini d.o.o'
category = 'news, politics, Serbia'
oldest_article = 2
+ language = _('Serbian')
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
From 7b87aaf55f777895043e678be806658d12701246 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 01:48:52 -0800
Subject: [PATCH 19/65] IGN:Add donate button to User Manual
---
src/calibre/manual/templates/layout.html | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 src/calibre/manual/templates/layout.html
diff --git a/src/calibre/manual/templates/layout.html b/src/calibre/manual/templates/layout.html
new file mode 100644
index 0000000000..3564357684
--- /dev/null
+++ b/src/calibre/manual/templates/layout.html
@@ -0,0 +1,12 @@
+{% extends "!layout.html" %}
+{% block sidebarlogo %}
+{{ super() }}
+
+
+{% endblock %}
+
From ded6c02dba002ae98d60c31c5dcbeb86f489cb0f Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 11:24:15 -0800
Subject: [PATCH 20/65] Fix nasty bug in handling of dates in the database.
This can prevent calibre from starting. An update is highly recommended
---
src/calibre/library/sqlite.py | 39 +++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py
index 938e5e665c..cc30f6dd5c 100644
--- a/src/calibre/library/sqlite.py
+++ b/src/calibre/library/sqlite.py
@@ -12,11 +12,50 @@
from threading import Thread
from Queue import Queue
from threading import RLock
+from datetime import tzinfo, datetime, timedelta
from calibre.library import title_sort
global_lock = RLock()
+def convert_timestamp(val):
+ datepart, timepart = val.split(' ')
+ tz, mult = None, 1
+ x = timepart.split('+')
+ if len(x) > 1:
+ timepart, tz = x
+ else:
+ x = timepart.split('-')
+ if len(x) > 1:
+ timepart, tz = x
+ mult = -1
+
+ year, month, day = map(int, datepart.split("-"))
+ timepart_full = timepart.split(".")
+ hours, minutes, seconds = map(int, timepart_full[0].split(":"))
+ if len(timepart_full) == 2:
+ microseconds = int(timepart_full[1])
+ else:
+ microseconds = 0
+ if tz is not None:
+ h, m = map(int, tz.split(':'))
+ delta = timedelta(minutes=mult*(60*h + m))
+ tz = type('CustomTZ', (tzinfo,), {'utcoffset':lambda self, dt:delta,
+ 'dst':lambda self,dt:timedelta(0)})()
+
+ val = datetime(year, month, day, hours, minutes, seconds, microseconds,
+ tzinfo=tz)
+ if tz is not None:
+ val = datetime(*(val.utctimetuple()[:6]))
+ return val
+
+def adapt_datetime(dt):
+ dt = datetime(*(dt.utctimetuple()[:6]))
+ return dt.isoformat(' ')
+
+sqlite.register_adapter(datetime, adapt_datetime)
+sqlite.register_converter('timestamp', convert_timestamp)
+
class Concatenate(object):
'''String concatenation aggregator for sqlite'''
def __init__(self, sep=','):
From 759c17bec3971ad01bd7b38c18c393bf1037e77e Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 11:33:25 -0800
Subject: [PATCH 21/65] Fix #1826 (sending to SD card on reader)
---
src/calibre/devices/prs505/books.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/calibre/devices/prs505/books.py b/src/calibre/devices/prs505/books.py
index b63b089fdd..06d205fb02 100644
--- a/src/calibre/devices/prs505/books.py
+++ b/src/calibre/devices/prs505/books.py
@@ -186,7 +186,10 @@ def add_book(self, info, name, size, ctime):
node = self.document.createElement(self.prefix + "text")
mime = MIME_MAP[name.rpartition('.')[-1].lower()]
cid = self.max_id()+1
- sourceid = str(self[0].sourceid) if len(self) else "1"
+ try:
+ sourceid = str(self[0].sourceid) if len(self) else '1'
+ except:
+ sourceid = '1'
attrs = {
"title" : info["title"],
'titleSorter' : sortable_title(info['title']),
From 2b4a4a31e362e03d5f6e1b1faafa349eb1792857 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 11:46:30 -0800
Subject: [PATCH 22/65] Fix #1823 (Calibre crashes on import of Mobipocket
file)
---
src/calibre/library/database2.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 00da8f3e37..388a2d4fdb 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -19,7 +19,8 @@
from calibre.library.database import LibraryDatabase
from calibre.library.sqlite import connect, IntegrityError
from calibre.utils.search_query_parser import SearchQueryParser
-from calibre.ebooks.metadata import string_to_authors, authors_to_string, MetaInformation
+from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
+ MetaInformation, authors_to_sort_string
from calibre.ebooks.metadata.meta import get_metadata, set_metadata, \
metadata_from_formats
from calibre.ebooks.metadata.opf2 import OPFCreator
@@ -1197,11 +1198,17 @@ def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True):
def import_book(self, mi, formats, notify=True):
series_index = 1 if mi.series_index is None else mi.series_index
+ if not mi.title:
+ mi.title = _('Unknown')
if not mi.authors:
mi.authors = [_('Unknown')]
- aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
+ aus = mi.author_sort if mi.author_sort else authors_to_sort_string(mi.authors)
+ if isinstance(aus, str):
+ aus = aus.decode(preferred_encoding, 'replace')
+ title = mi.title if isinstance(mi.title, unicode) else \
+ mi.title.decode(preferred_encoding, 'replace')
obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)',
- (mi.title, None, series_index, aus))
+ (title, None, series_index, aus))
id = obj.lastrowid
self.data.books_added([id], self.conn)
self.set_path(id, True)
From fbb6a3917eb66ebce310edd06847158928dcc241 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 13:26:38 -0800
Subject: [PATCH 23/65] IGN:Show meaningful error if user tries to set cover
from format with no format selected
---
src/calibre/gui2/dialogs/metadata_single.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 385e105c3a..78415f3a19 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -113,6 +113,10 @@ def remove_format(self, x):
def set_cover(self):
row = self.formats.currentRow()
fmt = self.formats.item(row)
+ if fmt is None:
+ error_dialog(self, _('No format selected'),
+ _('No format selected')).exec_()
+ return
ext = fmt.ext.lower()
if fmt.path is None:
stream = self.db.format(self.row, ext, as_file=True)
@@ -121,7 +125,7 @@ def set_cover(self):
try:
mi = get_metadata(stream, ext)
except:
- error_dialog(self, _('Could not read metadata'),
+ error_dialog(self, _('Could not read metadata'),
_('Could not read metadata from %s format')%ext).exec_()
return
cdata = None
From 2a3fe21cf74c1f501b02d28221f8d7ba216961c0 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 13:53:12 -0800
Subject: [PATCH 24/65] IGN:typo
---
src/calibre/manual/faq.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst
index b5ec52dd7c..c85ffb058d 100644
--- a/src/calibre/manual/faq.rst
+++ b/src/calibre/manual/faq.rst
@@ -146,7 +146,7 @@ When you first run |app|, it will ask you for a folder in which to store your bo
Why doesn't |app| let me store books in my own directory structure?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The whole point if |app|'s library management features is that they provide an interface for locating books that is *much* more efficient than any possible directory scheme you could come up with for your collection. Indeed, once you become comfortable using |app|'s interface to find, sort and browse your collection, you wont ever feel the need to hunt through the files on your disk to find a book again. By managing books in its own directory struture of Author -> Title -> Book files, |app| is able to achieve a high level of reliability and standardization.
+The whole point of |app|'s library management features is that they provide an interface for locating books that is *much* more efficient than any possible directory scheme you could come up with for your collection. Indeed, once you become comfortable using |app|'s interface to find, sort and browse your collection, you wont ever feel the need to hunt through the files on your disk to find a book again. By managing books in its own directory struture of Author -> Title -> Book files, |app| is able to achieve a high level of reliability and standardization.
Why doesn't |app| have a column for foo?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
From b7ca8da79d2fa3d6fd049f473e09618c0641866b Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 14:50:27 -0800
Subject: [PATCH 25/65] Updated Ars Technica recipe
---
.../web/feeds/recipes/recipe_ars_technica.py | 110 ++++++++++--------
1 file changed, 60 insertions(+), 50 deletions(-)
diff --git a/src/calibre/web/feeds/recipes/recipe_ars_technica.py b/src/calibre/web/feeds/recipes/recipe_ars_technica.py
index 1360c6aa47..eb4e3f54f4 100644
--- a/src/calibre/web/feeds/recipes/recipe_ars_technica.py
+++ b/src/calibre/web/feeds/recipes/recipe_ars_technica.py
@@ -1,50 +1,60 @@
-#!/usr/bin/env python
-__license__ = 'GPL v3'
-__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
-__docformat__ = 'restructuredtext en'
-
-'''
-arstechnica.com
-'''
-
-from calibre.web.feeds.news import BasicNewsRecipe
-
-class ArsTechnica(BasicNewsRecipe):
- title = 'Ars Technica'
- description = 'The art of technology'
- oldest_article = 7
- language = _('English')
- no_stylesheets = True
- __author__ = 'Michael Warner'
- max_articles_per_feed = 100
- extra_css = """
-body {
- font: normal 19px/180% Times, serif;
-}
-
-h1, h2, h3, h4 {
- font: bold 28px/100% Verdana, Arial, Helvetica, sans-serif;
- margin-top: 19px
-}
-"""
- remove_tags = [
- dict(id="Masthead"),
- dict(id="Banner"),
- dict(id="Nav"),
- dict(name='div', attrs={'class':'ContentHeader'}),
- dict(name='img'),
- dict(name='div', attrs={'class':'Inset RelatedStories'}),
- dict(name='div', attrs={'class':'Tags'}),
- dict(name='div', attrs={'class':'PostOptions flat'}),
- dict(name='div', attrs={'class':'ContentFooter'}),
- dict(id="Sidebar"),
- dict(id="LatestPosts"),
- dict(id="Footer")]
- feeds = [(u'News and Features', u'http://feeds.arstechnica.com/arstechnica/BAaf'),
- (u'Nobel Intent (Science)', u'http://arstechnica.com/journals/science.rssx'),
- (u'Infinite Loop (Apple)', u'http://arstechnica.com/journals/apple.rssx'),
- (u'M-Dollar (Microsoft)', u'http://arstechnica.com/journals/microsoft.rssx'),
- (u'Open Ended (Linux)', u'http://arstechnica.com/journals/linux.rssx'),
- (u'Opposable Thumbs (Games)', u'http://arstechnica.com/journals/thumbs.rssx'),
- (u'Kit (Hardware)', u'http://arstechnica.com/journals/hardware.rssx'),
- (u'Journals', u'http://arstechnica.com/journals.rssx')]
+#!/usr/bin/env python
+
+__license__ = 'GPL v3'
+__copyright__ = '2008-2009, Darko Miletic '
+'''
+arstechnica.com
+'''
+
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class ArsTechnica2(BasicNewsRecipe):
+ title = u'Ars Technica'
+ language = _('English')
+ __author__ = 'Darko Miletic'
+ description = 'The art of technology'
+ publisher = 'Ars Technica'
+ category = 'news, IT, technology'
+ language = _('English')
+ oldest_article = 2
+ max_articles_per_feed = 100
+ no_stylesheets = True
+ encoding = 'utf8'
+ remove_javascript = True
+ use_embedded_content = False
+
+ html2lrf_options = [
+ '--comment', description
+ , '--category', category
+ , '--publisher', publisher
+ ]
+
+ html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
+
+ keep_only_tags = [dict(name='div', attrs={'id':['news-item-info','news-item']})]
+
+ remove_tags = [
+ dict(name=['object','link','embed'])
+ ,dict(name='div', attrs={'class':'related-stories'})
+ ]
+
+
+ feeds = [
+ (u'Infinite Loop (Apple content)' , u'http://feeds.arstechnica.com/arstechnica/apple/' )
+ ,(u'Opposable Thumbs (Gaming content)' , u'http://feeds.arstechnica.com/arstechnica/gaming/' )
+ ,(u'Gear and Gadgets' , u'http://feeds.arstechnica.com/arstechnica/gadgets/' )
+ ,(u'Chipster (Hardware content)' , u'http://feeds.arstechnica.com/arstechnica/hardware/' )
+ ,(u'Uptime (IT content)' , u'http://feeds.arstechnica.com/arstechnica/business/' )
+ ,(u'Open Ended (Open Source content)' , u'http://feeds.arstechnica.com/arstechnica/open-source/')
+ ,(u'One Microsoft Way' , u'http://feeds.arstechnica.com/arstechnica/microsoft/' )
+ ,(u'Nobel Intent (Science content)' , u'http://feeds.arstechnica.com/arstechnica/science/' )
+ ,(u'Law & Disorder (Tech policy content)' , u'http://feeds.arstechnica.com/arstechnica/tech-policy/')
+ ]
+
+ def preprocess_html(self, soup):
+ ftag = soup.find('div', attrs={'class':'news-item-byline'})
+ if ftag:
+ ftag.insert(4,'
')
+ for item in soup.findAll(style=True):
+ del item['style']
+ return soup
From e7cc8d990d0c46338de473da5dacb833ceb0d8b5 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 11 Feb 2009 15:48:24 -0800
Subject: [PATCH 26/65] IGN:Exherbo logo
---
.../trac/plugins/htdocs/images/exherbo_logo.png | Bin 0 -> 43283 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 src/calibre/trac/plugins/htdocs/images/exherbo_logo.png
diff --git a/src/calibre/trac/plugins/htdocs/images/exherbo_logo.png b/src/calibre/trac/plugins/htdocs/images/exherbo_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee0e525b93af69d8134d757171fdd21f3f58263a
GIT binary patch
literal 43283
zcmXtg1yq&K7wtvr(%lGNkW@-Kq(Qm`q`SMNMY=^okk*SJT|YotN;(DUOLup_;s4%q
zEtV2~^UchebN1PL4^e6=Z*VXvF(D8Lj=Y?-1_XlS4?fDEXy7ZK37Yr8UnuU9@>)>v
z#~*4D2|mYgk$dM3fl$C7Kaf6o=G1{NQh3PddT2UZd3c+;Swg(Mz1eM@?A^^xT`bw1
z-K@Xvi%>!!&mi*B5?Vf)yIJ14#5!c%i;art1k%yD%+U(Oox03+6XbqVx@H}eez8%E
z>gE%=X0g2g*|gZ~Hg|@1JLOmAcSmF9%MpB+|I~7EW8VIZPMBM`y1H?0@gx(yzj-6`
zo!3HogZn{4s0OA~82&OcLL1}3leCQ$5~QYZ;*ml8vE5{o1+#%@B#H9i@*)-Dh3umM
zO_zbX{xwv
zsSGhl{Bws@cyP|IxN6J?g{7*s_8cOqkp&Ci6Zz;wUUYV*INW*@_&h-n1V@C)vM7(Z
zieiV4n*9lRzzE(WjF?6-p@#DqE(_#KLAj9Bh=TbimiG-!cujNU|aI|2;V612y#fVqW#L=UfgV*Gd(vqk^X-U?E~rD1XRT-q%ZT6
z1L1MVj?Cmi2hd#Sc2)ZZw^8?fakhIV$gK=Z0E0W2vXP0Lc|#Z1=!AHYR(VlXO-<*I
zoWSS^JX*8ui6TyZelkk;S1^lab~1Yc$pH-5JQOyY8`#i*F7C`($${exF@7R|`78?G
zEgJt-2)-G;yAgJH33(O5o_Joeoujk68%^x?5P5&GH6i=8A7-2|aT|?fB%(N+5iC$>
zqDp9@vRce7E#w->B&3Vh9a0Lp#S_PdS>V7{1Jd2iI4#-}QH@ugwnWMIDvOASRCpg4
zWeU2ZwY0QInt7wU6e47bZ15$25=sV!_$pm<21`I3x?jUqEQrQhLxw#i+=F!^1Lr7ZnB%oJ`_*Jyx0>R~wfA%|{^FGO%
zc;Cgo3Wv4UJjFB#uyxGL$~wBea#y)8Gi(eO4Y+lL2KHk&fe9lAMCQ#Uu
z*^eEH@8jz$S)@fLBt&UzYwPwy>klu)9%+}evz81yT$UM*W~(-=kq1xLDEfYNb(Q>r
zg@vWYdN>?H>`q@a*EvlV#|uH*el-Z*TwntP6kF6aRE;q1kUMhKyt3a&BdL*=DND
zkOiUu{q$NDQUsZV6f#|0WKwW*DW9XDFqH*9pYl2G?&?x+QOXucFBdO_V6P^+dqU9R
zq%U=JbcCGd>gzgd!75ZiZHl!^H^Z>Vcmnsqzw!$T3a;s{!&;}FLhSzB1z2}0VSj#&
z)E$yKxzZm`$EpTC%G)P*sT@{DRoNJyvcAJGp)3h3X&Z)$Nq
zrKaAzzq@%Ku&?<;B}IRA@vPAhqixG@@r~60cDseDxXW@dMcU`H%cB)7X8Wl!IA&N#
z0_S%m8?Bea5Qr+A__-uBi`yFaZIKq7Mpa8Id39OW$9tjD{0$`)RsRjQs-ohz6xO#%
z`Uu6FyP+p0QkIuP+~x(Pz1hLqy~SAeDZWWg3r*>E9nP(H-8#g}NcCi^`~=&LQoYiD
zy1T<^1YCkoj{Z$g%dLYj~6JrVKp+wAal|Lv;6&d5=_Be#_0D%j{z^#
zPDqf9-9WC^awt>KA9V?BA_o4PghpRq-*<0@*QjT###U4QM9}}TVlt;r0iGX6#h;E=c+u~mnMq#M$jGSP_l#A@bNhQOha?mYhw{0kc9JIYHsQPu2CQ^VCA@tkG!HAV
zsQ`^hB@>@vn7le$!kixyR-RQW}S`Y^h$Y^a)FM
zm{bH0ic1K-TPD7S@xBFlthecANO>i$udXin@87>`aKEcDS6KFw);LUS*c(g2LP26M
zJ6LG8`TH~F8Ev^yl#W0Weh4NBtI5t}X+!iu$q)HO6
z8N4Os-hXm4=fpNTOmMUH*!{<)9+Lj`>q>&yy?C?lne>Zq2QH9;1R5QW4jWQDxeJ#>yDOrT?(3rRYTTIn6!sK?;4$J3xc0EHvWg}>
zJzVNgE7Hop8t?q&xuXVGASu23{{4GoVjLw02M06pSU?!{)2EB?mS@^*kPt`{Qar}u
zx24Q%kkb#pc2Benoklu1U&cxte#{H>U{5J5WWK#zNytZ;t9PX*eX08_nuFbAYfR=V
zj{d?k5fKAovEb{jpUKR`nDzDba+DwOKsAzTa9a!Y;z!Q?8)Fd96$o+pgm32J+(a>E
zei)+4q711BQJj5$vc3fBj9!;;(7lLkJathDUKzfmiJ4gl$SBdv9E^;z6Yqq6`TTZu
zcmFJcrqgn{j1?LYv2rpjMphLR!fuO=09$A8RX6N|+|ZNU$qDg-B(<3~jZaUf%f5M|
z36qwVRs(4;EB+y#R?JAPjKOt(PEg%_XRe{zrlC1zf4;H*^}Py`XVG5%ff^kh-DWTn
zvc+)B$1HRc)iZ>MC1Wea?Nd>Rnt~=|U}cPtogFve=KOCxmA32H*w{#eyLFS#QHVQj
zp8Fco*?hp&@fw%?s7(EQca)yQqtoUM_(4ypsSpKpu9+4TQ20b%~isw=7h
z5?@tHO%Y6hy+v%wFu~Eq<92`h{&aWxbG1p47OVepFH^nW1^b2ti0yc|Vw#(iF|`5<
zqtX`$IyyQSfryflHG@N(KO`r+jFw6@j*|ChXVAfXV_iHqL8w?TGHPQ9S0AS=7wQuT
zGkiKUu@~ayZc9r|9Sh2U`Ed4YPF}V*+iPOe<
zNWQW=UClH&I0!#jo-z(vXR+O-V~@Y#0CbrxwBL?7~7eKqT6S*46GCDteV>GO$V=
z_16MA>AkrLZcNX5C5r0ACUo8M3g7$qG*v!b{`MxpCJd8A;KJM0HBU816yEi)ja4
zs0xIY^VMkTQ(>=2_HdB6^EcilXJ%5{nVHcQBk(AB7v|*9ZUmB&lAeyi6>FZW
zWD2z3Sj_$Mw2x}3k!b7Qr7ZP5+kPC)?_$4~^&GOf)}O|^KHc=*z5O1mF7$T&t22+2
z`a8<6{LTedR#yIIKadI){x_tQAsA8~50@O*^An2^lPM_wK3NsQUc0#mbfTi!Iu4qe
zni-jyw<`3M;}%{tJ-L5r)uY<
zX%wvz-JpO1nw*ZytGsuU%e>c59u^_p1-xzZ3s|I13-{r8-)S#WSbv-cArK{=C5
z;*i5$rW`f)Ll*fayT7a|qG9KhJrMaz}gWXxnv`%9;l#bh5
zTa%%uqeBUDQcPoCBRXJ!==?1__R8$})7N(}sG#h2(
zD7Q*KIP4hSFEQI2{%{76s{^o}O<2!d-6U
z1fiVe@Y<8k)Hx5=Ug1m1rH_VGDWzXCXeuN;IsEvGufasSM7O=zfn$n|!0&Vu#={_?
ztE+1Q(j62RH~jPG&(CPhQuW#UmO3IBj*6p5+4K-!om-avzWMnY>D8-OBP{`b1}*;Y
z)E;W>r`*^!3@F5fB_znh!{!pm-N=KFA@SmE-@G0Hsj-{8dr?C}gY4qP{`|_G{>$Z+
z72A!WtoL|Fk3izoK#P^6RI4b-h9k8>@9)pIyzEu1Q6QlyW~7ytmLit2-WF@S##Kjb
zw>R`EneBZRMw-Soxbrxn&mkHf8d7nwUAtHeYSmfBOMxK9{r&y?
zjmJgH!h8*}v$T|PUT9)rUJSgumd^&M?rF2@3fjTkL>spOC0gUt$1&aNF*9pQgB;gml?OlU!UzDtC6|Ahy%rRv}Xp7
zTHx7FF=bsnJu@)Z%*9&bp7C+b)z}3_1_o5xfKvje#g@ThS{O=41m4Ej1o3u&H$`{s
zoDQ`hP8$=#Cj!2Ad&!`fn!Fu!vOcJHMfP=f*Fot0v4u(pYhGSne%D}cZyq~4yAaio
ztBZ@z2CHqEw5+TKIFebb1|Ux@c&3_}nbogPhJiEDt0X!2f7hz{lLvO%=Yg3Sz2f(y
z$r`?(d($wVYxdK5@u1;)yebd+2|c_0_V)JtuGuQfnAQAu%z?ai6S;V_qQndbt}ESJ
zt4~l1e>jNx90uzI4h;<%_y@_JA?7R?^Y(kQf*sItm6m)%*o<2;?Gz;?e+%E~zk4^@
z)Fe`BE++$0-QINd29?>n2@q~Dy_b&LV=laK4S4`gFSRW-9p&WZ-#0I7lX+kB+E1?5
z($#_S3~t%K`?@&2`mA=bsv@5H5p5Ugxw~Lx2Csd=TlM1n`i6!Sa(YfqHR1=2lUe6h
z^%pOS>yBx;tzvDsl9G}(hs7S0K$YUi5FjN%1P4PX2?^=+T4Z{MhkuO!P@S4~nRr7R
zptWs;E3%2=s2%aV5K{bh%I~qfR3iO
z1}a5bV~(@`MbYd4obK^E6vH5be!+HqeVvDyS#GD&w~SiEC-M5`W=HxIbZ5m}IhU6{
zWn_6Dz+qRlBDBi^+2l4Di64?~pi)2^bo(i~H9KA-39SUPygKE)FyaAmRG1I2nU+
zvV^@ANV#9WjCGo+iHfIroiSN%oFgP8l&b67J~NYXeRVYo&gM9%t7D&;R24J=lCdrh
z7Jq<52?xm{KRY`+fANa!dR!~W_I!8RAb>C5*Cqbd=OO6Z7?bo5_{U-pdY;;o!$WZ!
z8|Hz50qK}#&mC=SYJtPjpjNO6JGP18ohv;FTn_&@GljizgME!BLCVYneVI@{?8OT#
z4&xSSJ3CeY7eM=s50C-qZB1b}_+F00aOl)d6SqkKh>Rs|Wm%>3I9W~#=Oi_>ty1%lvv$d5+8UbC=pQN5*o$}4|y;<8vuf0Oa
zyphYBQVlrf-RZdM*4}KLXNI+r5ly+t~AwC|D1Ck(i&K-vZ#ogH#8uF$(Hd^GR4e
zqb4)4M`t7f4ldeZ4M-7wUu8kD3p2^AcI9=Mn%RGz9)n{U254D?egAMK*HXY$OG6afLR8&Il>qTY3qTnDS6n-V9xI`+nh7k8BPfQHtuUFGtPuR59OW?i
z`^!Vv-a>QZ^N?xKG-qUIv-=K3MMc%9WCv~>xyNoXQkI>wA;d$9Drc2
zk9oC+peC(rH0?*1Z7)(GgPpy+-YHkFb27X=>Y+{Q1ofgjf;KP`#QLbem4!tvsM7V`
z2iZ2FiXlIp9db2_E30|atSLN`e~WcSK?f}@BV)Vwf}S3EzR{b^VBE-z5ONAnR=D}_>NLEH?lQ;C
zmdABjy68lFFoSpTQkckp5Kv3`GBKp!wI<&UR8(-D?$7HN$Mm0U3_pU}$D-LeAT5Pz
zf)77UpNr!3h#18Zg?$$au47^*>*(*tV$-Wh6rtt&-jM(>lukx#BfAtD9U4(
z{pr)Esd5>N?%rwWVT$Dt~)fOroD=(8^i)4|cvb!U=It5ol;
z`7DS`GmyKITCGQtR+2UTi*Tgq{C99DEcNjpvUrYYfCu9_MD3d_4kY;FM`WTK?+TOl
zC(ZbN5IJS#F2JEFIDXS8;xzBZ#-`+fzJLE-s85O05d(I&suGgt7i)Of=xWR3dumg3
z;`33m-MT}N!5uU?{ulG!Zat-HSi=f-9v7YTHDxNRn#
zG!Rx~a(Zt0K`A5LIXqUT#u>msB|RC((4XiE?9F0Nf=&qV}ZGUla_ytuNuQRWt`2
z1QZ7+U}dp8{%MT$Ft(S~ik4(#?VSAMjgeoC%8f!L!?ObZUSQwX(Jx=`cCGzm_
zkpJb&m)Kz50E7jDzNxCZ8tmSO*jQ|cIOy}h*5ZH(<^p2Q#oK%8UCV3VQ+!VnN9CM-
zpVn!CPFbV&wa+J?K9?GvErK*^3c6;9iGT{-`1K6AwVT0!{fN1Ko77
z;8!2?&;p%K9RQf8meddHg`dY}E0TnpLw9x!IV0Z&(uf+rlYO@4SKD$=$;V>4eeGU&Lt
z2*Cb~t80%{1l)K%#yiMvLNT_crXL2s@_*AQH_E??jg4Kcso2UAeD}5u(K}^oUq1cD
z$cgcuf0$^C!yKi{&CN|xNeNr@YLy7A_nSmKz{Q}Tq5YnnrCL~6DAfFc%c@;c==ulH
zJ~*H|>>B;{#ud?qWYFY8z?y;?itlI*4UlIs{27NFes5k9@<1N^#@12qKZhu8>!D3%
zdfR`s_GH%=2j!ZT9HICj0Kq&0{QS$tfEzv#Dopi3b$Nd3#YU<_d(H;Bai9IVG2*?q
z#u%u@0;##J1`vOT2M2Lp=#)u=21R_~i1>E|$kQeIb+{lOzR}am;QLLBmP^VjD`C>q
zy&3KSg`JN)I}cvM+t{6{#X?5IPLu)L>wf|gYScOhgUqFH$1yHV;oA5|ot$5BXziWS
zmsp#LiTDy-=cF7)v4DJup*s)0wlxPR6N-n|^M%ViR}Cr(e~mo@0lI7uot$MS4hN4PfR+I)C2S95NAh699mjh!&N$r+5a3r?+vv8
zApcVTKP6)08E6aKQ%2&hY5Rm|C!aXg9uNe)x3{+fXGQAY(5R`YQF>M-LDFzt>m%`b
z{~lv&Asb&qYB!Vo`Xd+S{Y?ht<>h7Mxf~lh;CvH>!=@bO>IpC~F!XNZ0hF1Wn)|J-
z*4p=144?W0EW)3mp>U8~*$sb%0vwkQ3hV*3#?@g5p67ob6hWnOJzAEzyuLo(`w8LH
z4oN2W4dKFU=@!1dJ`;?)-eedZ9rfjX5C*CZ8HW*ZSAyD@5$0_4$jpC)CH=N93-stf
zKUEI`0CI-43ls;$Hkd7^L
zy|X--5uizcfRPX2&gNVLHzkh^gpACTjKmS}cpV_9uT)-pJsFYBPbPn5t`&mUMit!M
zNW^%#oRpecP@4bBz!w$-g4(sf%Hu
z9*zMqFg$4%0Qmj=K--mY!m@?3o^a)~ZqucOf!xaHZ6q^0Ip7;$ejWL9`Ffhg_#KhaDg#
zrKB*_mzTY(w!+Dwker14Qv8A|#rXIPCz$0Q?+eJ`=o?y$eS0&r4=TuY&ulcHjJJug
z!yB!g^@6VCV*E%8fl??pK#d5wA%%l24!H?m_=g15eg2b&q~)?tZT}GIOyn+#i0OFi
zmD*1O@?%qnkaAZANuW`9e2TEBv8BHa&@(b3HG=nafNcInf2MulH>ozCN=1#AxWR;H
zdtg__+4s1d&iWf@b3=@G>uK6(h&kmLATMKS#j=O7<|ae&nOIItH)+g?{Nw!4FuxQO
z=+xN#RVcnEiyxGf)tjSwNWEPqFzTEm+^sg1TH+9m8T*FbdJOFnq!oF}O49nbXO!Ug
z6wjA0o`is|FQ25N$kEGr_o|)k_K%>E2711X?Hi4fL_+casLgJ>+&@<#e=C5SYQEk
zH4a%WkD31*i)$0A(HYHWkFEZ-EF-U4VC87DoXhT-IZ*gGY(NbJ_;%u!vTkpwMG~5#
zi>=Ab$CtzZwU$x68q3CtR|m7vDpZr$F<3(sm$xiyzWqGd%g@LRmo&^s=M^#
zdv3pT2ld$I{b9&y)vsCmNCoK_$=}IX@@>Z$InB_7yglalkQ7KP<31ez#M04
zmKuFEJVftKHRW&rbDGzy`gLSxVsHOq`#j$5k-+(qVK;Z)fX`QA{+#?X!amdFr4GT1
zBHbzr3{b2rqZ~mQ%*}n)dpn#XX6&q}tC}T5Lh=H$xV@Ys`GrpWa7KJfNl1aKiBgC&
zyy!!1t!oN;kzOrpfl3yu;x5Rp^4DWk04`ODwI_!*HVQA26#FV(>}M7;RbXbwJ|Xf)
z-qyZh*W6svu|!?4u9g`Wy9HT?Qz2HHq9)|8~4Wl3>y2pTqL_L76OwKb^3ZjK9a
zZ%26izaXfP41amFgOcPySPUL16oU!@?0qL-7(GZ23_*TJ0A}0w#z=x$1Vk!$NeGcY
zy(xIX%SXnPf8u}I*4_14Qa5uH-~jcKQQt}00+b3UJ!GIrOuHpU1G?_Wb|~Bvo0{=b
zwi5_ik)VlFSe^w%7*HO;#l_ujGQc>{4k%7ei$C~TJ?9q}JpgZAP9*U&`(OrAX-kY(
zuxN%jTf!@&+E4cC7MB5+N9wux*cbz{Qt~4jj`T?{84(fD?fuOGm`k|IG7B+gECn|j
zlWKM-;9^zG`Xt~ADVGMFDUe?ZAf=hb${+HoWF^`U{VGf6G=0M7G{+pR2KX|}o&?&d
zuQK)kue}bu+2fbD?t8>vfVJxY&;rNTo2Trsseh`>jaEt<3cvNn7<&VSeCj(?SuQ&Yopdp_%|`^$p`@a6?69DtBY<}jx5^71MVvb%N%@vYwM>nYD(^9X+c
z;Zb4UgFEUE*i@6-%OhD{q6jR|4|N00sDUrxTQcPoX7{U5uF`BjcxC&GIOW(7z=CCp
z22cX$0bpJDrKjEbfcXHFUO72A#NPz`CxDy-h3WVBI1%8G{O_zC&&kR_9RBCL7!SB0
z>PLJD2yoP!t*LU_aBPZ@nOenWV@^Gv{c?m7^r#?t;S(!uxCb}^);11_d0^R-GBaZV
zI-wSDfqM@gKk@4{0$^Z5Z$W0A(_lsR!YiFprg>TD^o}QndQ{JJYpdbLv
z40?_(z=F80{vpT|3!-`d-ub}a64YMs>K((wc!7a|hZh%9jbVmvUU2`kx2H-q%#t+rp
z++NX$`bXw3I-;YagGHd67xCqajE`TxiC=#PulcHV#rgf4()b)``j!f+AqUYue$_(}J
zC6kjimS)4aEZV=I7=O#4x=tJdD6-OF`c=uhYBWFyB?~{z`ka;)@hB33KH+jL%@|Us
zNJ54?j)H=c3vg>iZEcF6W`|^+TCPG0tIzEv*A)925>TCGewC1GJZ6#_^GgZ}3J}+5
z+NFA7yAH1W$DAIZ@pavvV96Bn#0dB(|!4_G)EK=6!Im`aJxUZuPP9MmOLG&eiD
z7iF=Hvw*vu*?Q~tY9r_H{JcAfLGjB!_h%d&c#o27%gw&<<<(XD^fU!H%79*>0^}9F
zx36#i%|YvMo#G3YV-vX;E{qoS`6VVsMmlC@C}>!E0MD|j(qkeoC)XDLRG8CaQw{Kt
zc_7POZWouktPfBWYOywqk5i#I$Sx-1n_b?7rEvvZxq`|(4C>slCpGYt@GL!_jPR-F92VGXuRs%JSQq0i`FU{Oq`%MJHQ
z%4)Cgi2bm({P~cb5_alvmJ{<|FIO;yl#`Rw=3=WD8ZLDtPi&
zs4{okgVQgDu4ZR?3BJ7qj{0Bl7GK1IL|g98^&`*xj!LvB?s*rbl+*eO*H=Rn&T)m(
zGroLIi?~dak5o|-_i%fXPeg?OIWzdnca%=}b19j#o&A}f9&}`MTxpTtA>q3BYgSOkG`Ih)#uVoMH~<=d5lOE-w$9&k+dtbTs7tqz}*0L5+87zteV_`
zpmPldJk!zEs|bysU+uiG)j1j45AaOvZ5<5^4DhL_ioeCy6VcKFJ3eJeCCy`GREaM3C_=LB``p
za*@z%EGc>UZj!7(DlKuE1;cdx@L)dLW|DxW+Yi2x-aGzUA&Yh5cI&8Pq@MmvvzQh0ARpj
zDOWi^C_)hjd`Za6@Y(Nu5is`d?=%Du9#K8gNrO)@)791|gnn?z4^dfu<`!Ssm3C3h
z^3{I-u=B*W|4Xpb6Y!CQo-x015xt_CH0b7ce;UU((~fZegQT-?yT3n=_RPNM{P!w)
zbZ!Y50<)kErh(VG0xCy@URGZvTO`VBZ^qSV%-P&D>cAJT`-X;>VW`9*saym0{1!=N
z0kgde-8(U>r$L01>2WE5d8DJW)5VTB8*uaB?N`Swx#2u=`5xqS<_;kb6v1m5Gl0sG
z+enxLlu@B^tLXAd0}j(aU0t(LL}lUrE~~2*HssGhZ};Mb^U_2q{+vG{(aNxwiH}o4UiePOWCvQi>
zN7+1;HM^wsF6Q*mnlu6T8zJ!864FiQSZ5-yU%M`MV!%2WgqWC^&;gg_&+aptJXZBC
z<~Ia9n+dWE5lEdZ0|4P)gZmGsq$iJj%;~|fqeOnu7>fHf&YO?hwa8qhNDjf6pCV!K_2QTM4^R1VZunKETwVZ
zzR?4P-9tnAB~UMb9G|&J4bZrLH)fqw9JC7>e_9IVMw{gulj4YJVw_!z8lXLGm`rYHp98og~1e2}NWhPvD7
zcXZ*H&!!2rN9Hk26dB+wp3%fE_9!piWitQ<{1LnYiu}903JMTxR#iU02xOTqlgH2@
zJh?cb;
zF>~($^wC6~g&@m4Vt-ie{S2@}>dOy+_%@mU<(WAXIJQe)KO-!ciX|naM63|&G=;S~D-HK#3`w@{zI3D7pv0EKONCUbC%>shSEAyP1Y6_&WO^4?I6
zSVDpF*J#Q{P*pM)2!KY4(;ptlXt$f+B&Xb-Xa9iMJnQ$lVr}l^3#(R@t=UJh7S^6I
zz$Vl%HtS2RjZ}(U`RfeUjGWKmi}X9!jTnxWwkk`w6P_V4<0V>XCGHla4NWDE&&94g
z9k8=10ME1xG?RSPdEn>zQF((Gu&}`f
zHGulj#EsgMt;qe6F}i76UtKeuU1Z{N8>EwFVu;MvdDk&HMk$6vIcVSI;pD^)AVL^O
zt%_gLt*WZ3EaHX}aleuHE7*z#vt`V{;c!!6@-XY_1lW%EKkl~~CYNurq@&&?7k2t6
zO*fHp=|Q~=6bM^x0~m+`2r78Ed*%Y^N1Ea}HbjA!b@N{({RW+%#vw4vPMdLm@3KK{
z4aPB`yaRnqK8mp4`AZtHz>-QsO%7UtnVBfU>crC$C8hb}7aH8$HRmay#^v1q>)yfm
zp#F+cfuZHj2VaPP)ky8Q_cPPxOzn%R>NN_Z6_(5HE5Z>uF1z+0$qUFXZ#5f1Z-F@@
z*Cb%DzmJrAcN3`B!#eaSF(%S4{bfe$jk>Dq@}hnI)r)?7NzT0Sp3VKKSoa)|x>o=i
zOyex}?$A7({+5emIuT;rhNg_e4A-r(`J^qOp`jswgCm$$*K_l?Ykovp=B
zXlS}cI_9kj>Th;2_FwxQ``Q2o5GDRUFKEeL9D7_wZW8H>d<@u^8TV%`Gxd~tqn{{e
zH#RoR!I`QD&WC})hVtdV;eg_HWkuHrB`kT{4vVjS{fB&bK#CujOfR3F31MA#b>H>BAa4kGV*Lxev{<`U=
z+b=P*Nd$zL$#7RSd6k>!B(+#D0730sgUU)=KNekv$l^hqtu!9A5K$*^JTdxLCG3y<
zdlR)45;sfpqz&{t(e(m>F_xH1beCtv?|&CHH9vcR!%`5)_yIXO0nb!Z5{y-;e~-Ks
zTb|g%AIhPH6W_4kg{j+THl
za*~86c{zp#=Y3-${NA4U_+-?9r7;_zjL69^8SN#;ApG8zVG@NURjan3OeBdP7E>i4
z7NcU@di7!XH+L8E0z->rErtat4X7=fC6(Q@kh72ysR&$*cz6`(-GRUKqiUM2Leb$H
z>hK}JZK$%m#uQ(9my?mvXaDkHC`*`fcA}Vd&_xC2Ise+k(FvB(`ZJ#0h1vQ=hM1tb
zI*B$7$q#>7`CH%RXKp7aC>I;tHPvow3hk~uu6Ng-tE-0@AGEy4?8uod0iTWFNo}u3=ZKRf~&R1BYxryZ2<%7Z}
zbSa^}OoZl{2c-&C)c$N;f@XkQnB+;%m-3I27H^+saaFw6FZI`|sO(xT%ZW_>6zD>~
z{7Is7^WU??aa2Dbb3V$0LPoAAO%;~jmuRx-M7K}8kP#lU7ogvz=O{ltdZ(zU7#HaA
z)~Wshy>wK#0-oFAHOY6$VpF5NhI|@V*hvEmFg|d9veY;b|tRa{R4$0=13)S5=dh(R3z1
zO8t^GR`kOiSChk*Z5bq4=B1#5s0eyxN9)0iFmO?zW3+O$OBsl0ls~MtL9O;2yelvg
zjg(ht`?Qnh!o5uJ%H1t7-wV(C0Z?B_uDvNC%O*PJBNBz~Wp8puY3`8Qz4Ls7?-bhSJ3b7q8SPFW$
zF^+mT*eh*VbzF_{v9Pd?67o#K0yt`Ud@{?EQZol%DzqV~obX41x=HCTO4J7c@BZ}m
zen?MGpFDabiLOs=TjqH>3QNRcD@ztc$i&wan|=WHLtn0%
zbqJyP=3tc^xVZ}pt99AEmUlhdJU?ZV
zhj1YFX+XNI;B4>N>C(+X8WBhYPWU)LD6f*vVnoLvklSc=hOp#6!4fmrQ#?ugJBpH+
z1I<1+3~p>x-GAc&w!un36so`_qT5jx#Z5zuA@EbmU{8aYg?d-ei#NUVsn)BtJ3KlX
z{dcj*Wa>jW?56_@7Z-W4C40y=II#45?CJeD*<1^)!DLXftcwdLAt9l_-kMq{RKYe`
z`vXN)JIaG{3u>FK{4Th@vY3H2Uu~_bB5_}T%zw~H{u|_<{vzh||FqoQ2pp@)*@bNd
z1qJiWWcI>~10U|ZVe|2}B+0NBN{2DxECEO+FVp@5o&hK(rmy*I17N*fGsptA&(HYz
z`mE#>Bhc=XdJ4N;t#HWd=a!~Qi{~X9fcbVA(9M#+?(^Q7gqLu(bD+UJ_#rB@A%J^LbwaVf|6j3-iD!NIsr{h2AQ^nRj9GtBGt
zGXZ8P4wH}vTxNI#!~rpO4jKS*5ud}IHx^~ySKf5=F4yOtc`UDd)Y=QETkZs)m~{I(
z3o~<1_I7TfmR~S&;4wKHj{h6#>%+vMg!-8Yx*&oRe_cQlk)P?Rsf91Jp|pB1K0)xH
z?v3P0m0z^)O_owo^4fK>v~dL8=Y96w!#-0nGN17#a^%B-p=kQzN((B!P`|zNJTU$a
zOrH)@bdT2;3b|vfBxt@0><@Bv(PSC)9JkV
zneV*_iI`t!l{SY0Lo$8Q=AgfGed>$&Y(-h8ffz^^=6AVx7uxM5Dk=&(R>Yndz;D1n
zK%7JgSw%MC$c%+BUd~-&QrEf1L`O@iso?`4TVaCa;Ml-+E$AtXa>HI3UH$8K|KL-5
zS>@mV&gYkww4%m2Rly3?oH@pNnzu^Sbq`ASGceFUEsl2^A6FH-(^88Cgu9KV%9W}D
zi$0AL3Ow&P!dJ85cmlTD7G#l|o}zxuZ&z^%y%t|GJvcY}i%=3uHThY2B5u>DUB9sBMB
zhNvnOwu6MdHa6MYi^1Nl_{JQ}QwxE@VCPV>JolnEe{8VYahSAz4zD!BabW076HDs})TMvd(_T6}!`
zqsc?bX8>E!V~=)V?n*dmY&>+mcf>qtO!E>n)rx!DZbR0
z@@~BP1B3{GiRp+j123!}d2m2#hkn=nBc)*L=t#|Zip6;O1QzQ5#6)=N95_|EfQ9rU
zOe!2YV_8`BkzTfL4^50iXJYe-Jofk!wzHlO;uJQX?_Yvc#TNp`|t)oJ{Wu0?0Faj
z0q)5H(V`=HrC>eGANU&o)j88H634h_YRsrSzIq5oODevk9i;wA?vp7|V8VX
ziHC;G;_J9y?x-QD;;B`d5e!pluh>n$bWx+
zVE9uX>o;T~Ux_*vm+_KL0!o1z&+8ri3b@L6Y{yW5V?@@etOj+$fvr@1wiMH@@mI(E
zD25XW^^$fTUC993^EiFdtD6P3Td!?Sv(Hy?-@x@oKx3`gjD5!_4+Is_X1WSKGS<_T
zF2{2B4*SjjRw@!-G7ydPgR>a0;wS;p801O(ozS(gtlB~QBX4)+%XfKn1~0Ti;pq)S
zCtMcy0fh03Do?vV+T0%q4Gs*rTppT&a}n;n2!u3q5S&dnDdIv9er?QY0n-o;x=?%u
zU?1>l-F?Ny)$Y9*5CN`XJr+#BkQ0bWAOYkY_EoCufgK8Fv93l?c8<%*|F_B1@7#*iS7psBA-R;rkJP(
zkS4|fB1v%;8E{pp!fQ`&cfOGf+`5(Z`Y$k*6ZXudqLyu`w~ZiW_o;LdfBC^`u~z;4
zCG5WGJt{~wsFNfCOiy<#%%FKq
zu3-kwv@H}+E2V_LSBJ*McW%wX1pF=-H^!g``5+DbYxd(`@Jo0I`2}2aJa!XMdHJ~?
zzIMUN;G)_Wgdz&;J8CcyoOnyB`X$lj51t5m1)$
zRid=uV_{)Eo+NPPf@~s3BN`}nQI+*SnH^ZID1tFsI`Jh_A(xUd_qoZB&L-W~z}Kug
zWw+fAivVOk+@9c-88n1|t{W5xbhFqsN*r(!oedx_l^(}LlMEhs>^&^#U+|;96BPW@
zHz?7fz+fv1%Hm^aE$!^Q_}2qmfwj|RhlbeSlHF!@KA5(tDWuEWK$0OLd4aHklhT-@lP`ZtJnr(^HM0pbT&oh?gN$O)~P!
zm7#R|AT9R_pI1aVG8FcoT8@@y%_*`13xLI8;2w!fiy|;Dc7hJy7fHWFB;2j*JAF
zFSzBpK7?J$$<5tSsQ!%;+%E;G$#!ig%{cIp8-hUSvg=qOaV+quF1J4;#$OOSP{~O2
z30`m3=q1%WHIsi8reB1;*)y}QbOs0d`#S*Bd$>9Jt!@_px<4E07hy+$F9O~^TA-MH
zh=_=oynjyU2e}N>5YkqPz`hH^Xu$=w1%h}EDb;yNvamP%@1F@czn9A~944l)A*EEH
zH;V+`UY25kJ4xhp^z?a2KAE$L>Z~ci){MM9lvVs!AVB>&@eK;3mIa4#0*evX5^PXke_Y3Jwyl$o&HX2#AS8ADhgs
zE*YLy!syqJw(@<#S(ZoS%z+%5C<9@XUupy{?MFWhxDg3=F=hqb9WXTptOSa`od_=6Q(EkdG>bloa*kZ2CdUZ<0c=<{kstyr=}5@{)lG_2>-s89V!aoEX`IA8>civ9y9dk^;Qr5|E|<n&mMbF(1yU|Kkjrb7?*T{uRI67gb%gQ6506rvQN0E1FKnZaD
z1=QG{72to!@787}1{z%mxS>bM?-cuJNC2J^E2}Zm*nU840Li@q$W>Dvo^&Ue;`9nz
z0hLd=PieusYp5BXctbvZkNu{`{3v`6WC`m`99ag?0gUPD>iXx;A8{+Ib$dTxl%WdZ
z;7L(H1^k1qzJ3P)>fj1{`GY~3f#5`$Ar&y?3B3*n$RH1V)O+n^Xql-ZVHYGjk0
zB%lo#;v3F7r6nbtZmV)i89ZC1%MRGKk9Q!z6T*R^M)9c~3;a~E=p!dKGUKH$3T%}C
zqDrwGqJar&y2t(;1z^TxB5!A*HFIN!I9B4W|xs;MAiSvumP@&5CVTm>Thn
ztLWkwb*|-Qh&Z^qMxb5HL5c(R*+1HHPQ>G}k1xbSYg;_-LH1RX1nRIR`qxev@we1)Ll3A(rD24hMv3iRR
zY{Cf@{?q4jdpM-vD8E(bza7DeVE4sqLQJ@6RpFz5jCl(w$UC8QG}5ivd}t~Bp`f6T
zM0IU#KH4U){zB6uU(e^>QPKX~ZsqZ3>z84R*==;hc~U)Yt%GP_NYV@bx1sleNVL2giVjpKhzo8PU+>=*;{DvjxO+l
zPO5aFy%o;`#x}}5no;GzFx!V6!Eqdvm4c$fyLU4}$vgM*q5Xr4JUAmCL+es^ynSra8ArN@)o|^Sp
zj-ay??Yt!I1kL#Bz~{``)HWKO%@jH2Qf)6yI*Csy{aEz>Wtty8u=eAr<4MU|wIlG42k
z-z5;~5dpAk6v72koBe;v|IBKrs89e*8HI6eBg`B`ETkXY9;WBNiK|K9Xu864(~(*J
zvSWn^A(<&ADd6Hh+hoOl{{uC&gy}#H61&tV3g?1+!
z<-K{_VZ7(WKrs)j9A7j820J@DQRMThr=%`V(tQ-Yd!4`Lzl0MyIhy5L4E6^*<+f}|
zVje?Z>(voQ?FR+%!^xrj8I%{g@Q**5{<%CeAoCjg>(yD_=4zVT@isQm12EH@yqKAe
z`m;FNUBvaBa3P%Q*YDdE&A(&s-o4J-*B{HvKz_(y2>o2Ke=0^iz!tJFGbd;TaLDrL
zuoNxalFgW9nls|9ymIlQgbiC`l12?Y6bGu{17Q-@y0}GU^XA0A_tGAm0#TWd4Rtx-
zp>%TORe{Yrf4paJmZw#`seIyURr)NGyOG)7d2iZ4daWgqnSOL*#%Db}3x~G({)mPh
z==vcmtBy<2Pjv7^3}8`}ef{;^>`+zphD1iysF|P@{zN>CcKU&Hi@O#^yEjD<RD7g%gia
zacrS4wW_*onSJ&1YsaC{X2d0hh199f&wuAww=QaE>e)#36i&`3Pm&~kDNRn4#-tZs
zQdg1$CK|^Zx+!Y|>)T4+oD-;ih0k0hl$)RbUx$dXq574U$hDA=Rl+K6-2Sjk>rmVJd!k6KyE0Zqtw#IW=#;)q7cPjYZb@3ZK_jW`p{rovG5q8tk^lYV
zQN@uB2QSpYE3kt*0+9On^t7SR*LQm%WHy4uPM@BZ=4-$%YddLa>7zLC2s
zK!Oq4pZMQDU)OodoGMW0dMaKcEd1*CI^XfL>im-iUlTI)Y$9_~b~Gf5ZpII}CLnP0
zREDZcU%kqqyKbwpLRoGW6*28)iK)IOzTv=u_{@aM;335dayL%C-}UX8loX7R&Tr*0
zzz{aeyRKRS+8LPGgHYXITp9iJDGDl8#t_}cy-qBVIf@QV6f$ie$T!CFrWuhK=wREv
z9EhXLP-UtuUNmj1ud1BCxVSj~iQ{3z=f1wU_K#>EJk*{L3>U
z4Vuf(Zl>-x)w{F1-o?$W<#Y@ElMIzLymSn@1|faE%#<59ZcrLmR-Cs67CgI7RgyBy
zse^jD1-o>jc>T7)q@*Mq4yCiJN2jL3?w%RxLf#r+#b$J^Xg%Q^(}mvJ>hI5p=;+|o
zR6d9kqS4-oQmHp*wN9Nng(HcWRZFL3oU!nRYL3UYy;s??#r@}RZ!2r7-WTWod^sF_
zO)==@n~J4WcI70N9o{TO*}E9kABEnirypSr5Dk<&*>&=HgSyY@w~^C7et1?HH7@)dNmF(T
z)pvqAM|>cxqWplF*=3~3RIk%9GvkbmilTBxn3$Q-#_vA(Ij^`l60BIN`Vw}&=Wmud
zv=?f~1bXxKEg6gl-e62=Lf8t|{Y%T=b}i3$D{#hc-J%%y^Y0&*;ry8XdVFz54fl<@
ztu49N|L_P<>e+R<8z)m`$A-tkXg{`v%Hl1Q{#x-}t~_E;x0=Ime!6dy*O!4kIiEA}
z8%5OA_+P(%tz2mwQ7S1b+Xh(712SC%AMibT^yu~kX(=8(3UU6l@_77Fl*dBh#DiV0
z@0?om_0Lbo8x;z7M5mUuba&X&r_d0@4IJkl58SRKnB^mu=^(TNX(Lmay0iachP
zqkUsxbRTujHD0?W_~Y~Iki00~Di>d<*l%h`KIJ9Q_Ocw~7JN>Fv
z$sxZC3=ERBo;`U|>cwhcif}NViwhrvFmgb!NDl#jXnDE3mzP(U{N&5Tp&i;67Z?F;
z5y%`vb!R=v)F59DV2tx&A7}8;4$vE#y{(jp7LM98^=yy#3YDAr+t=1Ge)UV+_*Qmg*^@0NtFo*p^Qzc&71{W|h6_58c#S2Z5X9r4T+d^|i2W1Z%Fei?$``nlap?ScHNaaT
z@T$V3f~
zjBEkk-Ysu$Z}0beA;MfM;YImj&4x>V1?%hUmHW~$XMFtlPrMR|U#*>;5qBR3aTjvv
zehk?@ptR(yew@myN#!)~{6Qh!&~#1s{P*lB
znSX%k&hhi>JD%}%c1#HylqJU=j|Jq_NA5p1-p)ZsSlHr
z&8L5RMch(Z&9*)oe%t}IFDRQNcMH2EWb2uCiYf;p{*&!K$+y0oCZ!_Wb5%&4b5E{S
ze+Rp>*H!L0fv|_Pluh1c83F>1EkjjlTP|%YMNm&!tn%3vzvZzn^3Sb+SP*MQM@M0o
z5{k4HrgT;zwWgE^Bkn)|nxhE7YxoF(#10daIDIF)!f5zU&Yp~K)2FMX2Dg1X6`s>n
z(7JWi9J5eBckhW8^zYlFo7+6zD|*d2z~2(9^6FuhM#$I9EOEh-oE+Ny`}eP1Z|i+^
zH_k3dL@yUSsbNc&WCv;30#xt9nq4exY`c)<)b<|qALD+e;)kBbvXm3L;S-&-M&}i-
z*9tUASWllAs-(6@3q^c$YIo=jq)}Afk}KE!_31hkNvrXiD;Pc<-_{q)7@~=zM{m{)
zEtku1uKqW47%k|H`L~YW+yk9#zfCC2=MdDcNB68&UA*(%j)v;k$!>~4$eAz&iu<~z
zyM7KX4$TevPy9@m8-UivFIZ4VMr=0%NdGa+n-D9d>6riTq_>P0eZJZ`u5-EkKx?JWLr)|f`51xO+4gxGW}qY{COr_MM;~1hkGz6&
z;`+7=myxja?U){F1e#PA26fnyj-cI_wJ{g(r0k%Tidm$OYM&qgLSFEJxH2fg7a?L
zImRYE%60u$rJ3dv?GW)Bm|me-rfEt$W0R(g3=-Qqvnu0rCr`?TvI<8+;=8`otYs_U
zl(5*?SpDM$w)0EhoR-li5WJKaOaO4ZwT6_4s;djY*P)w2zl!c*Amu!p2kkM&z>Sps
zOw_U4(Iz4so|AJy)mneKDDB@*tvOu$+awJ75J2$Zv9VK~%Hn=GH*W?Zg_R9Ee*9@D
zB#otq?l3?6x8hr8#)JllP#Pyso^?^|W14q$3l=hBq
z7H{S~2-yVI{j|Li>>@w&)J|}D-nZY!_=`u!p7Kv;d2v5g4dCUfWOZ%dB~Lc8Zngcnn`111f{i6beoixv3#&
ziRAly&L_WJiwH}C(IGWaOk5oFDH{%ZU3{!8sn4?PjFo^;$zvLH$pn_v{O+CI%-YlW
z!sT%1ha#QRs9tM<#lZ9cBeiQ*PvC}$%yQR~(7VI#u#X0PX`Q2?9t|Vla9%$V5^f$rxx;2A?5J&ic>&PC)%fHZYW=Pv&JtIzK;;BP*<0E)Trt(W6I`wEq3BT2Rm0%!7YEe*CCoX~_p%`Y~7|2bir9
z<b_}mhp%e>bG`I5{SdC{TMk@F41cc03x2ySj39`lO_mKLVD
z(7Ze^G!^|gGox#1d3W{#{_w<|NA?0}Si%4^jgO}-(!Hv5qywMQ8Mzl0w_t=3iP`LV
zL^;Hz%V}j=nrp^A)0-ln9VP$kQsY%x=i%+gmPS~bgvN&)BFJ0CUN*J0jmB|GR0EvD
zPDhT=0zEa())q!PowCel+QN*=f92*)>RN#Xy|}H~mzKlv$Ts`@2)uW4vkmbhlk*rZ
zYB#}R=21fra
zcW&QyN3JZSSPozkR4f|$2M5WZSOszM$4{SX9LqVyo#C`3{}-I9g8>@7^(8&)*{4Kd
zQ#46j_!G$nqLvF_=iWTp4icYPJ{*sxUOdO+1s^70jOAz1*Cwc)I;a_y7I
ztFdg+z{}Ia31xlu2L+LzPp_3KRMjM%zwY|~wE#7VULV!=tu>+5nV#EYE!ue_UUXLr
z6iR?p-QC@rz+62?CtX-rNMYa5g8Dy>EJd&uZ78-EKA9Ki{zyR579Qs~&e7!JU-!f+
zy{-PJAj7V=uP^av;ft}eyR$}G3naS)$B(TU$SEm_ii?XgV*Aa!W(@~ePsmPhg2589
zD$0Ux4qX?|Hcf^SqzA&ms_GKiW0$M9>;bI@2qP{b@%={J8SEF
zFbrk6-sf|%*AF8#Q8=-nQfotrR4b|P8klM1mXVP$3^ipx-Xk*>sPUksK(ZKImP$-t
ze}5Z%X}w4U6RF+nnZi>z?a5duH2moedk_t35B@9*z@7!dI0
zVZg)un0CaeMuc)|e;OLL5$8Poc8bc%mtlaz0P=X~6+XdydYu6slw~2sG;EMy;M?BH^^nEx1$cMjn
zt@?!U=h<0VLko8eiob67LNa6WLBlNDu>;%#{5loa+JQhCAM4Quf(W)rA4t8&!bj6|
z4PPBU)wzY~1G0zfxk0JGdBF%u;3^Ub(qEhMA(1u|vS!pOsRPH^!m}!5v$QCLI#w=K
zqC~8gj?dJXo}+$AFn|B`%lLRp9Th*f6^>6*Ca^og&iW?+ySj|cNT^Ijya@a>^7HYs
z8uyl%gH9)ukCyKbxTow{@49kgzZ|pLL(2vyw_mQ3>mIZk>Xe_O<*rC
z5-sl{6+mPWs6(C}61t|`5XrUqZdeTz+faB#jbaq`jMKYkjE-NV^TwIRU!f`?`>$_n
zMsUKn4|`(_AKbYNz6#)eOLuq4n@_)g|0Y>O5UR5}IXUsKTXzc>g7Y(^sZSIi-pGXw
zgDIQE7StuSP~O0ggf$VDOY31VXq~Xi=L-WeXf2P
zV*CmUJR<#!lr`9da8}sU%p3Qca-&mlIC;|cr9~x%7@qYqzMntsGBXPL(z8+Bv#kO`{sDP`lz-CFb&1b*o2ZR9pk5e*jWYAJuc55HDbg@uI4
zVLWm&K*Q1os#2i4NQPZ^?%Vra7LiwqN=j~F94BBNievx4fGJXssvj$4TP`jwo(N_K
zlahQGkxxKP5N;Cq=1iZQ?LBs4M<0&%w6eA~9G}}0RrI&(fx(?@jmf#~=W5+d`_yWG
ze4TodF1poz@{=GI9xzan*Ey0uM>1BFpv!H1S{PVVBz61tZ852QRaU$iW^XxGdp@0*
z3vVGuI9@7ArP9Es9z7{(s>>Io6NvZRkypders4OP2W^Mzi?#61J!)KQ`Ln10o
zReGO2i>+EKZuF5H8^{MB0Uv%DW^$bCHd*
z)lu&J>$WQ%$Qk12=XV%s^dG8rUV~roOEpYYSi?&PWk6h@*lTI6DLykprPbF*uU@6A
zSQ{lJ0gb<%+9j8-w@w|EJ{+F)D>aol(2uf;
z!n}R^%X@i=+1byoo%%lw3^`e^8H;<9m*ChM>bSD}>&}pOzFQ}X95=c&6p69HRjMUw?j};D%?)4z=zy0`ZgZvN4ZmzlA4=??ldwXu=
zCOQYdj23m!b;-F`Uu+MKW2&w!P!-uqijEIOVl1HN0nbC;i0A$N!C*E***363c)D}L
zHcY34cg6iF$dlNKUjmZA)WpPO%=ES3HrgP{wQJX)NEJu;Qxxb@BI%pF-uLWkZrn!ZMV4UzMA4Rg9k2#5xn+R74QgSNt3A!;>@_Rc?&gyoQRM_WA8nFa21%9DK
z)9}-{{Px3C~*Q5C3#j
zQV48Y8m>vf?BMpfu{amU7MVT?j@;1DU~=$a+g(HET{!#3Mn?m8T;V?Qwv`F>kALgF
zoW@BRn&l0F+LViQihq}fSl;~NqTR?G_?0h}jVhKU>YurBa~Rqfd&*`S<15iW#+_*l
zO!>9_)JXInX7O&XJh|AT&dy^faOBrv`nHO>SbPdA9=-|(IETEC#M*Jre*O$X>(8~&
z2MoQ8lo6gSOq30jyEJcT=MKHQdri%IDckaU&BjL2b03W3Gs)_M)Wt;pP)8@-|Ha-B
zO(x0)bGl>(l0HveXYYF1~3IQTM@}YwVonkv!$zQ5hm87`yG%-=w!ZYZ?G5yIRz)p`Y^cFahzde$S{9=&
z^BkC&+S7Aq>@0+^F=r?r=$1HiJ{!>-4adynGdmOno1R`KcWJINaKoio)6XL)?oeEbS63!*02m}i
zAh-c`t9;imUQ$Y`nLJK-;S5<_Pl&S7oZJ+AzSB(V6jo6ZHBgTtQR8MRmk-insNuq9
z7-r>MQ#Xin0(7yQjG-d(fSg;>(w6~^n@Gy~+Wocyw+Jq}u%rY%)GdJb+dqB$Na7*M
z$N0ZCp|GeZxkgk)g$L@WhGPf1HPO3}utcPET|pdgu5i{3V4wAc%P(Y*
ztBt{Y7miM3=ueJs&jz7RWaYO{zrBW!yW0?&99(#EUR}?Gn-D=b-9)6MBF-G(W}>zY
zPueh1U!7zwe~vBSymnNGvQCw!59%Hkq!wIBPq%(G0NUwL89h2Ra?wXe$8OMh%?Lz>
zWZl5gk;;%_xgQH^bY>*h`c6S3W6bC`D=O@%^^xqCF=Bvq?f$;CRyI6K`&NbVfckP4`zAAwt6)IU
z1T!H2jliZp9U|1=L^^U$1ev^cT@{MAHl)CrA%qZ%ix=EAK6~5rZ*x>o~
zofc8_Ls;DKn#5fr9&jDdK{9Bf|EIw!=NA;zwXwPXx(>7|niM)j+>HzeolrRSiH5
zGM0(fZ)uihjK)DDYy70S`f}K)iww|a;@H7hp${HTpxYO5<@n)d^5Xz>wa+ik@Ejkz
zDiY5Kd1woozxd3|ClOZ_(s9cLY*viI!ooe?QoqQf#I{4pm>A}^FxuS-LlzMlpz8
zWT30&GQmWI#`S0|I2h>x2Map+<>5LwswDP*P-R*|PVZ2a69e4T
z&n|fFM-IC?ZdzEoPBr>~mQmZ*1zf3gNWkbkN=>GkM^0A;zk%sJSc7A1B5*EWiHeGP
zva&NYG&Bw04dCJxgo}3S7v$nS;Fgy)#4~-8PGr^W-@7*ih_Zf9qXgO+
zqV7&tsmg}t8|}p{b^vezT-LBifSdOiYguK@y{DhX{Ys8xgxi^bx3OoO#VZW=qKXlZ
z5%Onmq3{?#GVeq%oaN^~I66m-&pD%VkkmH-8*&8~(Z2(Ri@_x%-x4G(hDq0nIS^mB
z>8?~M7w%qCKl@?#oo9CDO|7VkRUYqmreAj@ad7u7ZzywbuCbT=J&DkjH*RLnpBnO#H2tv`=c&TpC=wF4fwx87p
zM{)s6nwD>E%lga%+#y6tLYmU~`C^}vXzGa0o1q7X8W%eDP(RI`x0xlq^R|XcMbr3?rcN%Ay#Iu38g>czqyoTZI
zr{IT|FVm8zVOehL*te|+iT6^+o}^Lv?o_0a{7bxo|89K)0d~)1Oy9IWVP>`8^jyK_
z=eLhMVn)-rQA;c9`|Y#%?x36o)XH;ng9iuBkeFt)Pb8ob4S(je6!bl4$wyJ^U>)gM
z=wEGQWW+8k9D`?hs7_ddGoliAIbo~m&eXw?P+7ZTzr_cbF#1+mz9|C`m~C*2aBF{y
z$&n;UZ@^NmkP19A;PilZk|r|eqUB=#Xbp!P#p^~%b5j!q0I1v_byd}rzT992w)Gpe
zwe51Ms&~f~vqt8S+*Tlow*vmHWX@=u`yzzB7}jeN!8-d%2({___YW)^l=_r!LYxMa
zw0-f@i`0W>-rQ3{@hzy%9(mv!Mo!ml?Ts4P^{X&HG(L3*!Ow64XLufL4=Q3yzKc2K
ze#yleuaNC&5vzTD|9BRB#D>kRl}kLTS-_1Usb@AWlx%vGc;YR^(5Bw^9dlS(dOEPJ
zHJQ^UTL_@Le*IOP!-s4bHUX2y)h@7Z=Bori7U0Zr4A*eD`}m}EH@4s_w&9FJJFSBI
zgkrI09C0?Dl!#=_lbp%udMD|Os}
z$%UXg%X>fKii;+U3PW2GxqQC7iGb*|TXusEZ?vEaGfda8x^~H(cU1<-CLI^!tkc
zo_aH&tag*W7!6AUQAC()U5;A@2FYz*8j)`G+;f(qy4z41ifMI2YSssMAf
zAhLg(p*I^c8hX7__rza-mU6q>2zVE}u*SiFml?R|0R3pabQk%h<51w5J-c-_TwDf?
z{1|v%{HC)hcjLel5N_e^+bphM^E!rxOkfhifRkgw`*w3)
zO3KOvN$$#d3{3n&$@xywq@-nJwDuhs_f+=J*aap-T7)A0u3
zme3OR*?cFS8M(tCT3C}8J&1F|!*}SPk*Zku@(8j
z(yhehVmQV7nIESmCSHNm^pJInl_WP|TZpk9CbcM>S2gpRsxG}0Y#`Z^bsKnjcb>>N
z_>PQ45GXpFKHcLhscHkKloJpo<40+x+~zOE0^?g#^Iz%s`E9$U8sug9Gq3l7b%!8G
zR$}-dx2=-g#E7c|U4sscd0jJ*qF5l*Xx9H5h|gMyWA=ek^)U!H!YWz(kv;)=?7+aC
zQVXKo0){Kw?b(?5{_9sZWV)YxTYtx5*=|pYtmCcq*4F%a6djv-vs{lQbXr(Vw?!mS
z^NFpYXv#mLbWM?xGe~^Y`X+RWYVrTbeLkR8nD2}nK6o$~+YU%if4yQi)(N?{m{_Y2
zZ*2ANFSJhIkoK1J$KuO}51|;PEEYTHbZ@d_^e*Np@dvV%r706rnG~uVwx5QPt~dT_
z67~;{@%nj9v_j;P(Sv8n#fjMW2a470Z{_EQf>jME75)0<%T=(~o@S~y@#e`5i)&d}
ztW%%zVhlw_unuZY8B>*5@J495Ant0xRIA38xK);_rk3XY3NSe_LA`YgBD90dllbpWi#9_;36%OG@_eJC
zeye1D2QLCvL78JrhtY$Na$|9Lv%1#R$jF5A0iU4^bNt*|F8OCE`FY+Rz6M0_ypfKP
z123#}>L|6gn~Nl$g65+06LLRG9N%$j{ImV
zc}54+tWvc!4k%m(1`G=
z!1H^iL@7md+SuU&-bom8IW&AqFKvF
zyIF*XC;1w~SqhaNa0Cty%a6H5NSPv?1S&eI?`Iw&~Scm+CE--4<8r}|XlO~KvRRRAlfeA}Ib3p7Es491F?>tSd6!sUPm+x&lqUU;i
zE8f4y>JwO~(pzknIB_JS@d%8+s-U!GOQ}js5wKxLBs>FJwV0H*s-!&p`(s^+@`;@O
z=bOaDI=mb8%|VDz@j)QV$HvhyHjcIpet;XI_#tMQw6j6Ec;B(X+}xaCO5nRnJ)hvw
zgMAAGT(PtMp8iI52V_nUfwI8rTv9FA36&Ks*q!=51Ilg5aLw#JZBQt{;cO9#vjnRw
zJ}s?KQRa6`i!LPHN$N{YO*;4s^QSCUf%RRrb?aTvhG`!cIGG6nWi1#xC$j&rtUy#d@)QAw#N&ZX_g
zOk)z1trovk4w_;!1SKYNm4)&R5V{Yxz$)pQBv8G$l9yiJ_~Au1C_FX&$cMc>-?LBTUSe3uR9
z#?)|i|Jm&0N449h#RX4(nXgdCR~(uqhE$%35I!ZYYOFZx>PzyJ`F^
z4Pq^(aMCQ?+^nghlAMwLm;L{10b*#k?>06L2D?YD{a3yC^UKpM8WdDrGHc04f1ZCwD(#1#s~3g@H;-R{!<7pZ7&e
z3oS+ysh@{{E1?;Ew7VSp3Ka4#U(NpBUUrBgn*d$Up@Z*QeAft%1c{FXHRc2e%sIw!
zf+qA%twvJ$oNKd%ivX1#6A{QxO-?Q@lGti7VX^a+_fp`JP7i(-x;^K~Su%f;=x<;=
z#xkA-5UV5T1HO1iImbwLT1c*PazrMkrbMyo2p0K2#Z_KvLvV&?3{>SW%MaU(E#i1r
z{<8T2mLdj}xcIdc@)Mz&3di(B?q&4!t&x+LZwFX4ii3m5GjYLLrg=?r=#S5?gZ1=*
z`?v#HD77!r9}wqoc2D8Y3+mtn3A?)QmOiJRxK2NnVR{~@%{St5z^7{5@S@+YJ9?W!`E2tK57a7VtG4$H_mqs9LC@=?
zP%MTq0ACm8I|9y8#uK$uA=moVNM!SZ>keZo0A
zK?j1iExau`idliQ&^xPt&c14G+g8M{=DzXEy9c=G>pJO2l>21l0QU{ucI6@?3+ic?
zUq*cMRNeJJ_IZx+$n@Lj9Sbz?ti_@N0MXSZ(0!$q4aFH|BzHv3<2Z4GIFF1Xo9fA>
z0g$PkNh0jATi#~Z!MA>xhTaxA;{v1TJF4L-&39V^pw7gt8{H~)JEjG
z={;zis-LUxipYG9k0_n^9twe5XP%&pqOjl6%4Q*xB_8M)8u-jJGD-xZM=?g#ZGj}y
z02Gl`mptntzN~}`7YXY1S_uK31#%-GX>jG-O|9$`D5EjB2eTRO
zk>sO_<((ZJO_*YoE-tu08P*Dk2#6z<9vMkV5C;x*^n;M$NQA6D(D>$WfWD=XyL)B7
z?;IqQ?Ram~#knjS7Avrbub}P@s9kj}<1!>~5S+34;H}_wc+764$FqTTN?z;%srxUncLMtrc_wApL
zV?%=LmNcARI+1)zpvrED3)WEY|;?B;_h6gwOL1%OO
z=W_asQtQp>KIPb2bq6_iSD$>61Ha|DbLX7RQfO~L#%pOg=yty~8G7$0k_Qi6=ejPo
z16?BY9fV?meo$cXU5Yrm66op*mAF%OeCHZ1;nAij7T^4hvB!K^NL^OeI6vLjUi8zz
zgWKMrN8d&O(y@bWA=%hlI-V6R9d1$EGtS;g{1MELp|;G^vb+xej?~K6e=BUr%1Tpq
zj%*QIUk?>2M1SU|&pn2eog>?}XN*htGNmC$rVPrFE~8W=w-7|CA|?ifPhs<6SSSAr
z&^U`JK&?984U)H8?0>g?5`mbKWOc`dx%MU#;RAn&3-8Xv|gvP=G}TD
zxCh96Dn+7Qh+XDa4}Lv6x2LMMHuUb@t#JQKeGk`iRfnFXZtgW!zL60B0j3R%)M*|e
zZK~Lsrd9Q3G^5y_WQu@{>f48>>m+il^irxGp&I6zpUwfaD`a(GGX_3_EGX0`Clx?t
zv;?}>NoOj%u#admjv%J4h+rsPn%~bbcN6vbonr22ucrMLmOC3
zdvPn>jjWzk6a~A6(FZ2hQjp>)cMHj4=ts-p@)Y+Q(TxveH4KVWdrH4;ric5w
zsnEAB;oUmtf5nXlH@y?ei;Io^T0=UdVX;_GdX{`Dankl?e6K>f|yXNH4+ZD*d6utVfH)Jc3CAdDnDO>s1gu#1%-C^
zwtxbVU_`V-Bz9Qmbd*~?&g=?r{Aq6Ft1g65)aYD#^fOAI_x3iSfpxai%rHzO%64A4
zoBKgVDIqWIwRlDHG8^=lw_Tpo;y{)^cL(ofQCSeJD1-wJ1}{;j6!CS*b?iJBBJmG1
zrsGhYWX+h6K?OEL>#g1`&dEqm6q1mt+20Iatrr%M&2C29KRU-m{l#r?ioSKo&zkI3
zXa;yk{JzH+FKP#@2mzzc)v~{?Oatd_p4XDujl8@N7^%(OXEBf{59d4Gf3gHMT0G|4
zDa${3(fmrZ;NNsEUeqAwK~zpI?2TjMih1M~p6cq$Wo4)GhIfm^hr1r%@$AK>gL`>o
zq#`+W8vcn6N}VSLaIA18xLWYm3yX_$aNH5X*7ojhvCEM7`}z5~bmIajZtTC#9P=lD
zKex*KjXc2>BdAh5+K;A0&NOCSc^&dOa1B#W{Trx5oM6cJJiH-W0kf4ms2Spi0+ARV
z6_xmLwT>w_u=2GSYH^mNo*F(#*q$m}Oe>LJyD3{;pGg)v2Y7-143um^6cuS
zAe8AH9NYyg`*UO3C%G+f{UN<*%*Lb(tx9{^di
ziIb~%_HSnrxJGirxUHq21~5yDnb(@OIEbQg4M_5dvV0djcLen1UZy*PeOfo@jSF_%
z_QbH_aGGi`kl6UH5fdMKX<3;gZoE8u_Pd6)hYBV+?BwX*7xqMAj7~g#{vn5Mqs-qO
zf(rNeniwfprrnNV8b0KJ5DXkDg&rqC*|xN_^a2suXYrggd``#NI%;_v#qh@Ajb4QW
zGN|{^->=3gbEBkW4KCZ*0#xtC6z(B+0cU}H6|r+8nfG)p%5*XHj80E8?ce{cwh~{H
zT+Ik^rXv)9F7Iq_j%((IvJ-75ddtq-paU4O-!=Mo>jzJzZr|O|Li}0R=ol%z;SsC6
zu(QDjwE$ntEk9cZB_YnU>iPsEjgJtqrNvRX!}Uo%q4Jg!6B9R(7nj-Q95CI0CnW8^
zviQu~14lN=Si;aca;yoFloMLnY+`E%vSxm!J#4Xf9qZW5yKrWY&3`(t%7LkIvUP|EAoBtRD8=(k;`
zYu#LFo%AfVXMLe|U#%Gq5PbYNPWp0y5r`Y-I{JbM01B>jEzxt#5h4R2Dwh9
zGk3ga-oU;!#xY@)Y}A#ude)J=$Kup&+c3z?5-lloW?@i?Di(j~RKe$l1z8V3_8EhX
zkSac1$P5$?+t1iZqsT&;%CH8Wri)ZT>tq2wKFd=w?K4CJv<;l_9fr6O96Qi@_FZ4c
z$=UL8bxH0qqo@;7BrswK8HO;GJTEqvDYbui-865%YUk6Z21v9e%9qbQyBM3nji-fo
z(9g|&;&TmCWY@K$OQXG!7Gyf`qS4R53H^u>XsZDdR$?IrurzSn>80A8epzEA~H@(&28C`
zVEROmI>8%wfua_%-xIr4SC;Zkcnd}{P@|kNv5+J{f4}Lb70$6lSA+eoN-8SO3vyA8
zBt&!>smbNlR-H!F;Mov0
zr6?63>V#_r8xgy=if3c+Ml|Q>lam{s=m(z>$)RSDtQZ8=&-5
zO0o$E2momyQ3g~0iU6EQ3&~4}i8Zyihq@|s3JM@(s9^rp4KzK`oUTbFeqRbt!NCS&
zLJJCr;`up;Lpvyd>VO%CAwdPF&_w=UH)qA7`ZYJ`o9c?+9ep~+^{vcPo6?@K-~KEj
zG@e}7T8AZfGD?0IC`jwfONh}ZEiENia24&-v6VW&0Tsn$?yO~iOdjgAx^V9FP9RZ;Hal10X%U%LaK6V7h&&fBA
zdAiec$-Uz8@~r=*Y}!3AvT2762-$MzC#ok=)ySBSy+&egpq2{JYCG2a^PVNI?>})f
z^X?>GBin19nt|DB32eT;zQBr=W=?uT699yF|A$3o0CsVinWc*2e&4@;zgbqcSEhQ$
z;lsjU08EzZzx&NYcu&FxFoly$1me8u_ChO(Hs#yL=b`{SSa3s>td+EHz1Q?*gbYcZ
zyzl|c&XY{_#G*92;5fI3BJO=W6PvTyJO|CV7g+<>Q7j(%a_auq*qooE;2cP-XBh^h
zT7_gdgjuHTtr2T~`O?BMQ)^?n|GR4EJ%>sbaR}f=H8nK_(sjFSLZLvKeGonmEQn7#
z)e&2%N=Z`LaskRGgr;~b&X|Y%w&2ASfcb?iLBnr_ov-MGD(1SS
zNRrPAZIgAt7HmHB{nhQ99l`9xCV{uykdpiWD+~urTgD{~B1!ro-TU_KgV$e!R3Ad5i-)L*ukn+W#5;Gr2)=W#!Kq1fqGmF=PsPvmGIKasD@NCOHO7EF_u-A+3YT4{(bKg#>kX=|LbR8&ARMj8$;>o@0tO*5B
zJ=gQ|eBIA?zNEIHz)CoO@WT|f1PJ-ls!Je^Yf}%h@+$^H@I$|N4VUIzgFd4A*i%7r
z=kKo6ZDce!{YDKJ#?5wI{Q-AZ7o0*YXDf>B?r3b%X$_pdhZ-euNfAKlL;AKOcEnRXeA
zJ_0~CjVdwZ0f$x(?h)#SpYbUQy>Z7A4Zs9*7auHI0?dWmK78mmxSMT1hMbOh8UAZO
zHX!!x`+w14o
zF!^b96uu&6!9@{!b93i&MmxU0I-acrwzhA)NE(gI`?9<5j$p=t(W~wDkOe{Mu3wxf
zs?_MpXhn!*8#=b9h4mV1&;Mm*c-rFA%)lNmHkz%N?{dd#+j=&POf0^t6`1Am~rd2?r17qNpuSK;~Ad(hzMUy^u%_(#AMU=~IZfdHeTnsZb;LnednH3Msa
zqSfnWFo{CBC5vHv5`+DsJ>8$*G~X0tWZnbW@o7kn39ob?(+y=g@lwDAnJE2=fI4ex
z4YDl;EU#P$5~bfB7`}~OcJ=ItLrq{o_N98mf3Yqk^Knr2LX4P*E_TZF@LdJ;SrUj)
zS#cX_)Izrbcl>pX{SxaM-AJhhRt=;`5AB@mcMlkegw6}?7p$%
zh0;(IG=8|{VQjn>DT=U}*-__AUxWs*E-Bm)(wjI}G&5;rBj7d%{OXX8d|#aW;rh9W
z#QpOhZ^c$a*@t<1GR?Y{rS7Fpu8DQ+$#m-@Y;*dRN7z`o{3zyS3tvB_QVgsnesURN
z0TOTMp#2%^XV&_a2zjt259PvquAGf2)r{rj;{XP>llN10F|$_}eEEMRU3)x}Z67zo
zLSp1x(e!qbP@OSCozW%A(`ZuNH&Lx#N??~
zjlI9U@BZEAv-`ex-Pd*fe&5ptxX}K~fp{p(v*~na=v$!o4S-
zAcTe^_mj#jmz_z;9PMXqKx-l!rEw4KWjgGB0?wcTQkU_^$%o*bZ(W*OMdOW47`eEO
zl_-bz-4p|+)*z2eyMWklWfD423NuUzajuq7MB){yoHgFJCM5o+nWGx9+FTSPZ`O|_SKe%&jJD(
zrj_MPe9SyFyzR9dWrYVSKUrNx=OpQ{!(RL_y%rp82A;qCb)zQf1}&al|yA1B9=AD~&1RrlU3f)#C#xSg-Ue8kI>$?q3wo@l)|j2e94Fy^_8J
zQUej;naoAf@Vi_kI5OAgU$#swiB+r67{T%pIBZq|fJWSp`B{8n9_Rz^0V{7uyeGmZ+a$UFdD3h8bu+5pk
zXkk&qgNRsW{p(Wln35JXh2;HE%LzU2}la+hk}--GglCf*S>p{z!+;EkW~9Uj^+32#29#
zG{v4D6ziG5mrR7@6;u14qs*h#LSj
zsI|ET%^nn1fWsF5sp~>_J`^2s6bR#`jQ1`%_6a0L`3%mJe)01Ap^p
z*jq|^8$1~B?-cOioLnqOLBRR=8us{#M}0@|_`4u)%k7!;fSv2
zg}c=XC5n%X!)xgScodqYSw)9U3;@ORIa70S+~@e4f1Y`?D00#!_@L+Qz_g
zoPFa)8zhuK*0l4=B2;ql2n~<_Jc38t3fZBR#H>f&K4uldk;`SE9-b@#)Om
z*a)JDW25HHSAA$|RjjOXt~Zr-VWyu~em_}fttWM}1JxJ+dO#(_I5c233iT0R-~d)J
za|P{BKj)uSBB9V2dpA9nMpJJKet3%z3{DdYYynmz(-j}g89}`5smtF9z)AScm^za+
znOc31jmo(e6mLN~dDhZZ&~k2M4$dBc-!9YPGxGDDpg$6ubRc?jAI>sCXlYxR#boY;
z`m^Izj{Yxxn_^G7sX)@#xJ}VthGe2H5`9mUTq*O=v$ef*a8qpZpM?ga`E76T-vHEf
zhlql}z#X4@J?r&YctT-SB&9f9TBL=$!na7Fop7@n*61YrFn^P_s71*tq64!cTrD}T
zNHARarf22APLcUc+78xS2*-lxr&r%L>$7bLjI-Y{hj?-c*g#!j7V
zxq$O3$eJKVDRlf};tXS{YiCyoOF4AQ=f6kxe1s}htl)a`#1xx))9ipb-=*mC1H(>L
zq3#mheeYTugx@Ir>er)4sFhMLZkvR8-!B7o^^LYb&K5(%%u5TbRFqCUKOiR{yQsdt
z0CP00cDp-NLWAk_vHM1s6a(JW}{9zDY)?DF`Oi6p`&tc(qs{
zL>GH~HOBU4ZfrVd+FLTGGk>_XeS^?<>v1LC!(kX*agKgRmQF8Zfw)x5Rxp`ophyLd
zHXUl&NDw8tPs-REPziq;@keYMw92`38tsKdVp`uTTS~48y~bo$)N)jnVsrP$V7A`H
zQXO^mgV2O6#WlfFHt6l-bk+0ETd@BEX9YdwmkQAGxe|$8wG#!ldbS$z8a+pIhe^~V
zeSsRUA~r#Cs%PN3f4hPEP^b7dt#H!_Zk7hp_qAC0-nlY8DS=v-Qn^R1ZZH?3P!sT^Z`ok21Dp^MREH?U7PM5QUO&r{??
ze*4p=DqJ%;v5kQcYK4dTL{9B`F;W{nZhp5_D2+r8$Vwh_At#w_jA^1&R$SEVXj(
z7@4+3@`lNeKb=$Om+Vk3grVrKhhMaB5+0CUrZhjXR!?<93FY~<%-@XhE;H0|en>}{
zx3yNCcAfmWc+B5>H^@X1NVVf>bt74{3@J5a?Fb_sIg-XpSp(i8jVWB0F-A
za2J|O+MO4WA0$3Ji~E-8d_fPhgS|*eC2Pf&(hpp5hZX_m1BDBW@MOUP3+DM|Nm|y+KK&nPwa-zAyHOuCnfot_+e~R
zUyR0>*Z;n+T90Na)akTQSq(k2W`3xSabclH_p@Ss0q`;C7VnsANE>orOrA1teWv6L
z`yl(E0Z(Z+y#8?AG|Sbv)d;o`g>K?MUqF28o!)0zC*SxIbwuFkbNQO%hXnxD_U&0-_&K4~E-2|NfTdijZFe36Z3%DdY#cXTD-<8